]> git.basschouten.com Git - openhab-addons.git/blob
487247778e3b8f96ce9d22666d7b2e5bb40a6f21
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 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.digitalstrom.internal.lib.manager.impl;
14
15 import java.net.HttpURLConnection;
16
17 import org.openhab.binding.digitalstrom.internal.lib.config.Config;
18 import org.openhab.binding.digitalstrom.internal.lib.listener.ConnectionListener;
19 import org.openhab.binding.digitalstrom.internal.lib.manager.ConnectionManager;
20 import org.openhab.binding.digitalstrom.internal.lib.serverconnection.DsAPI;
21 import org.openhab.binding.digitalstrom.internal.lib.serverconnection.HttpTransport;
22 import org.openhab.binding.digitalstrom.internal.lib.serverconnection.impl.DsAPIImpl;
23 import org.openhab.binding.digitalstrom.internal.lib.serverconnection.impl.HttpTransportImpl;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
26
27 import com.google.gson.JsonArray;
28 import com.google.gson.JsonObject;
29
30 /**
31  * The {@link ConnectionManagerImpl} is the implementation of the {@link ConnectionManager}.
32  *
33  * @author Michael Ochel - Initial contribution
34  * @author Matthias Siegele - Initial contribution
35  */
36 public class ConnectionManagerImpl implements ConnectionManager {
37
38     /**
39      * Query to get all enabled application tokens. Can be executed with {@link DsAPI#query(String, String)} or
40      * {@link DsAPI#query2(String, String)}.
41      */
42     public final String QUERY_GET_ENABLED_APPLICATION_TOKENS = "/system/security/applicationTokens/enabled/*(*)";
43     private final Logger logger = LoggerFactory.getLogger(ConnectionManagerImpl.class);
44
45     private Config config;
46     private ConnectionListener connListener;
47     private HttpTransport transport;
48     private String sessionToken;
49     private Boolean connectionEstablished = false;
50     private boolean genAppToken;
51     private DsAPI digitalSTROMClient;
52
53     /**
54      * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String)}, but the connection
55      * timeout and read timeout can be set, too.
56      *
57      * @param hostArddress (must not be null)
58      * @param connectTimeout (if connectTimeout is lower than 0 the {@link Config#DEFAULT_CONNECTION_TIMEOUT} will be
59      *            set)
60      * @param readTimeout (if readTimeout is lower than 0 the {@link Config#DEFAULT_CONNECTION_TIMEOUT} will be set)
61      * @param username (can be null, if application token is set)
62      * @param password (can be null, if application token is set
63      * @param applicationToken (can be null, if username and password is set)
64      * @see #ConnectionManagerImpl(String, String, String, String)
65      */
66     public ConnectionManagerImpl(String hostArddress, int connectTimeout, int readTimeout, String username,
67             String password, String applicationToken) {
68         init(hostArddress, connectTimeout, readTimeout, username, password, applicationToken, false);
69     }
70
71     /**
72      * Creates a new {@link ConnectionManagerImpl} through a {@link Config} object, which has all configurations set.
73      *
74      * @param config (must not be null)
75      */
76     public ConnectionManagerImpl(Config config) {
77         init(config, false);
78     }
79
80     /**
81      * The same constructor like {@link #ConnectionManagerImpl(Config)}, but a {@link ConnectionListener} can be
82      * registered, too.
83      *
84      * @param config (must not be null)
85      * @param connectionListener (can be null)
86      * @see #ConnectionManagerImpl(Config)
87      */
88     public ConnectionManagerImpl(Config config, ConnectionListener connectionListener) {
89         this.connListener = connectionListener;
90         init(config, false);
91     }
92
93     /**
94      * The same constructor like {@link #ConnectionManagerImpl(Config, ConnectionListener)}, but through genApToken it
95      * can be set, if an application token will be automatically generated.
96      *
97      * @param config (must not be null)
98      * @param connectionListener (can be null)
99      * @param genAppToken (true = application token will be generated, otherwise false)
100      * @see #ConnectionManagerImpl(Config, ConnectionListener)
101      */
102     public ConnectionManagerImpl(Config config, ConnectionListener connectionListener, boolean genAppToken) {
103         this.connListener = connectionListener;
104         this.genAppToken = genAppToken;
105         init(config, false);
106     }
107
108     /**
109      * Creates a new {@link ConnectionManagerImpl} with the given parameters, which are needed to create the
110      * {@link HttpTransport} and to login into the digitalSTROM server. If the application token is null and the
111      * username and password are valid, an application token will be automatically generated or an existing application
112      * token for the at {@link Config#getApplicationName()} set application name will be set.
113      *
114      * @param hostAddress (must not be null)
115      * @param username (can be null, if application token is set)
116      * @param password (can be null, if application token is set
117      * @param applicationToken (can be null, if username and password is set)
118      */
119     public ConnectionManagerImpl(String hostAddress, String username, String password, String applicationToken) {
120         init(hostAddress, -1, -1, username, password, applicationToken, false);
121     }
122
123     /**
124      * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String)}, but without username
125      * and password.
126      *
127      * @param hostAddress (must not be null)
128      * @param applicationToken (must not be null)
129      */
130     public ConnectionManagerImpl(String hostAddress, String applicationToken) {
131         init(hostAddress, -1, -1, null, null, applicationToken, false);
132     }
133
134     /**
135      * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String)}, but without application
136      * token.
137      *
138      * @param hostAddress (must not be null)
139      * @param username (must not be null)
140      * @param password (must not be null)
141      * @see #ConnectionManagerImpl(String, String, String, String)
142      */
143     public ConnectionManagerImpl(String hostAddress, String username, String password) {
144         init(hostAddress, -1, -1, username, password, null, false);
145     }
146
147     /**
148      * The same constructor like {@link #ConnectionManagerImpl(String, String, String)}, but a
149      * {@link ConnectionListener} can be set, too.
150      *
151      * @param hostAddress (must not be null)
152      * @param username (must not be null)
153      * @param password (must not be null)
154      * @param connectionListener (can be null)
155      * @see #ConnectionManagerImpl(String, String, String)
156      */
157     public ConnectionManagerImpl(String hostAddress, String username, String password,
158             ConnectionListener connectionListener) {
159         this.connListener = connectionListener;
160         init(hostAddress, -1, -1, username, password, null, false);
161     }
162
163     /**
164      * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String)}, but a
165      * {@link ConnectionListener} can be set, too.
166      *
167      * @param hostAddress (must not be null)
168      * @param username (can be null, if application token is set)
169      * @param password (can be null, if application token is set)
170      * @param applicationToken (can be null, if username and password is set)
171      * @param connectionListener (can be null)
172      * @see #ConnectionManagerImpl(String, String, String, String)
173      */
174     public ConnectionManagerImpl(String hostAddress, String username, String password, String applicationToken,
175             ConnectionListener connectionListener) {
176         this.connListener = connectionListener;
177         init(hostAddress, -1, -1, username, password, null, false);
178     }
179
180     /**
181      * The same constructor like {@link #ConnectionManagerImpl(String, String, String)}, but through genApToken it
182      * can be set, if an application token will be automatically generated.
183      *
184      * @param hostAddress (must not be null)
185      * @param username (must not be null)
186      * @param password (must not be null)
187      * @param genAppToken (true = application token will be generated, otherwise false)
188      * @see #ConnectionManagerImpl(String, String, String, String)
189      */
190     public ConnectionManagerImpl(String hostAddress, String username, String password, boolean genAppToken) {
191         this.genAppToken = genAppToken;
192         init(hostAddress, -1, -1, username, password, null, false);
193     }
194
195     /**
196      * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String)}, but through genApToken
197      * it can be set, if an application token will be automatically generated.
198      *
199      * @param hostAddress (must not be null)
200      * @param username (can be null, if application token is set)
201      * @param password (can be null, if application token is set)
202      * @param applicationToken (can be null, if username and password is set)
203      * @param genAppToken (true = application token will be generated, otherwise false)
204      * @see #ConnectionManagerImpl(String, String, String, String)
205      */
206     public ConnectionManagerImpl(String hostAddress, String username, String password, String applicationToken,
207             boolean genAppToken) {
208         this.genAppToken = genAppToken;
209         init(hostAddress, -1, -1, username, password, applicationToken, false);
210     }
211
212     /**
213      * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String, boolean)}, but through
214      * acceptAllCerts it can be set, if all SSL-Certificates will be accept.
215      *
216      * @param hostAddress (must not be null)
217      * @param username (can be null, if application token is set)
218      * @param password (can be null, if application token is set)
219      * @param applicationToken (can be null, if username and password is set)
220      * @param genAppToken (true = application token will be generated, otherwise false)
221      * @param acceptAllCerts (true = all SSL-Certificates will be accept, otherwise false)
222      * @see #ConnectionManagerImpl(String, String, String, String, boolean)
223      */
224     public ConnectionManagerImpl(String hostAddress, String username, String password, String applicationToken,
225             boolean genAppToken, boolean acceptAllCerts) {
226         this.genAppToken = genAppToken;
227         init(hostAddress, -1, -1, username, password, applicationToken, acceptAllCerts);
228     }
229
230     /**
231      * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String, boolean)}, but a
232      * {@link ConnectionListener} can be set, too.
233      *
234      * @param hostAddress (must not be null)
235      * @param username (can be null, if application token is set)
236      * @param password (can be null, if application token is set)
237      * @param applicationToken (can be null, if username and password is set)
238      * @param genAppToken (true = application token will be generated, otherwise false)
239      * @param connectionListener (can be null)
240      * @see #ConnectionManagerImpl(String, String, String, String, boolean)
241      */
242     public ConnectionManagerImpl(String hostAddress, String username, String password, String applicationToken,
243             boolean genAppToken, ConnectionListener connectionListener) {
244         this.connListener = connectionListener;
245         this.genAppToken = genAppToken;
246         init(hostAddress, -1, -1, username, password, applicationToken, false);
247     }
248
249     private void init(String hostAddress, int connectionTimeout, int readTimeout, String username, String password,
250             String applicationToken, boolean acceptAllCerts) {
251         config = new Config(hostAddress, username, password, applicationToken);
252         if (connectionTimeout >= 0) {
253             config.setConnectionTimeout(connectionTimeout);
254         }
255         if (readTimeout >= 0) {
256             config.setReadTimeout(readTimeout);
257         }
258         init(config, acceptAllCerts);
259     }
260
261     private void init(Config config, boolean acceptAllCerts) {
262         this.config = config;
263         this.transport = new HttpTransportImpl(this, acceptAllCerts);
264         this.digitalSTROMClient = new DsAPIImpl(transport);
265         if (this.genAppToken) {
266             this.onNotAuthenticated();
267         }
268     }
269
270     @Override
271     public HttpTransport getHttpTransport() {
272         return transport;
273     }
274
275     @Override
276     public DsAPI getDigitalSTROMAPI() {
277         return this.digitalSTROMClient;
278     }
279
280     @Override
281     public String getSessionToken() {
282         return this.sessionToken;
283     }
284
285     @Override
286     public String getNewSessionToken() {
287         if (this.genAppToken) {
288             String token = config.getAppToken();
289             if (token != null && !token.isBlank()) {
290                 sessionToken = this.digitalSTROMClient.loginApplication(token);
291             } else if (codeIsAuthentificationFaild()) {
292                 onNotAuthenticated();
293             }
294         } else {
295             sessionToken = this.digitalSTROMClient.login(this.config.getUserName(), this.config.getPassword());
296         }
297         return sessionToken;
298     }
299
300     @Override
301     public synchronized boolean checkConnection() {
302         return checkConnection(this.digitalSTROMClient.checkConnection(null));
303     }
304
305     private final short code = HttpURLConnection.HTTP_OK;
306
307     private boolean codeIsAuthentificationFaild() {
308         return this.code == HttpURLConnection.HTTP_FORBIDDEN;
309     }
310
311     @Override
312     public boolean checkConnection(int code) {
313         switch (code) {
314             case HttpURLConnection.HTTP_INTERNAL_ERROR:
315             case HttpURLConnection.HTTP_OK:
316                 if (!connectionEstablished) {
317                     onConnectionResumed();
318                 }
319                 break;
320             case HttpURLConnection.HTTP_UNAUTHORIZED:
321                 connectionEstablished = false;
322                 break;
323             case HttpURLConnection.HTTP_FORBIDDEN:
324                 getNewSessionToken();
325                 if (sessionToken != null) {
326                     if (!connectionEstablished) {
327                         onConnectionResumed();
328                     }
329                 } else {
330                     if (this.genAppToken) {
331                         onNotAuthenticated();
332                     }
333                     connectionEstablished = false;
334                 }
335                 break;
336             case ConnectionManager.MALFORMED_URL_EXCEPTION:
337                 onConnectionLost(ConnectionListener.INVALID_URL);
338                 break;
339             case ConnectionManager.CONNECTION_EXCEPTION:
340             case ConnectionManager.SOCKET_TIMEOUT_EXCEPTION:
341                 onConnectionLost(ConnectionListener.CONNECTON_TIMEOUT);
342                 break;
343             case ConnectionManager.SSL_HANDSHAKE_EXCEPTION:
344                 onConnectionLost(ConnectionListener.SSL_HANDSHAKE_ERROR);
345                 break;
346             case ConnectionManager.GENERAL_EXCEPTION:
347                 onConnectionLost(ConnectionListener.CONNECTION_LOST);
348                 break;
349             case ConnectionManager.UNKNOWN_HOST_EXCEPTION:
350                 onConnectionLost(ConnectionListener.UNKNOWN_HOST);
351                 break;
352             case ConnectionManager.AUTHENTIFICATION_PROBLEM:
353                 if (connListener != null) {
354                     if (config.getAppToken() != null) {
355                         connListener.onConnectionStateChange(ConnectionListener.NOT_AUTHENTICATED,
356                                 ConnectionListener.WRONG_APP_TOKEN);
357                     } else {
358                         connListener.onConnectionStateChange(ConnectionListener.NOT_AUTHENTICATED,
359                                 ConnectionListener.WRONG_USER_OR_PASSWORD);
360                     }
361                 }
362                 break;
363             case HttpURLConnection.HTTP_NOT_FOUND:
364                 onConnectionLost(ConnectionListener.HOST_NOT_FOUND);
365                 break;
366         }
367         return connectionEstablished;
368     }
369
370     @Override
371     public boolean connectionEstablished() {
372         return connectionEstablished;
373     }
374
375     /**
376      * This method is called whenever the connection to the digitalSTROM-Server is available,
377      * but requests are not allowed due to a missing or invalid authentication.
378      */
379     private void onNotAuthenticated() {
380         String applicationToken = null;
381         boolean isAuthenticated = false;
382         String token = config.getAppToken();
383         if (token != null && !token.isBlank()) {
384             sessionToken = digitalSTROMClient.loginApplication(token);
385             if (sessionToken != null) {
386                 isAuthenticated = true;
387             } else {
388                 if (connListener != null) {
389                     connListener.onConnectionStateChange(ConnectionListener.NOT_AUTHENTICATED,
390                             ConnectionListener.WRONG_APP_TOKEN);
391                     if (!checkUserPassword()) {
392                         return;
393                     }
394                 }
395             }
396         }
397         if (checkUserPassword()) {
398             if (!isAuthenticated) {
399                 // if an application-token for the application exists, use this application-token and test host is
400                 // reachable
401                 logger.debug("check existing application-tokens");
402                 sessionToken = digitalSTROMClient.login(config.getUserName(), config.getPassword());
403                 if (sessionToken != null) {
404                     JsonObject jObj = digitalSTROMClient.query(sessionToken, QUERY_GET_ENABLED_APPLICATION_TOKENS);
405
406                     if (jObj != null) {
407                         if (jObj.get("enabled") != null && jObj.get("enabled").isJsonArray()) {
408                             JsonArray jArray = jObj.get("enabled").getAsJsonArray();
409                             // application-token check
410                             for (int i = 0; i < jArray.size(); i++) {
411                                 JsonObject appToken = jArray.get(i).getAsJsonObject();
412                                 if (appToken.get("applicationName") != null && appToken.get("applicationName")
413                                         .getAsString().equals(config.getApplicationName())) {
414                                     // found application-token, set as application-token
415                                     applicationToken = appToken.get("token").getAsString();
416                                     logger.debug("found application-token {} for application {}", applicationToken,
417                                             config.getApplicationName());
418                                     break;
419                                 }
420                             }
421                         }
422                         if (applicationToken == null) {
423                             // no token found, generate applicationToken
424                             applicationToken = this.digitalSTROMClient
425                                     .requestAppplicationToken(config.getApplicationName());
426                             logger.debug(
427                                     "no application-token for application {} found, generate an application-token {}",
428                                     config.getApplicationName(), applicationToken);
429                             if (applicationToken != null && !applicationToken.isBlank()) {
430                                 // enable applicationToken
431                                 if (!digitalSTROMClient.enableApplicationToken(applicationToken,
432                                         digitalSTROMClient.login(config.getUserName(), config.getPassword()))) {
433                                     // if enable failed set application-token = null so thats not will be set
434                                     applicationToken = null;
435                                 }
436                             }
437                         }
438                         if (applicationToken != null) {
439                             logger.debug("application-token can be used");
440                             config.setAppToken(applicationToken);
441                             isAuthenticated = true;
442                         }
443                     }
444                 } else {
445                     if (connListener != null) {
446                         connListener.onConnectionStateChange(ConnectionListener.NOT_AUTHENTICATED,
447                                 ConnectionListener.WRONG_USER_OR_PASSWORD);
448                         return;
449                     }
450                 }
451             }
452             // remove password and username, to don't store them persistently
453             if (isAuthenticated) {
454                 config.removeUsernameAndPassword();
455                 if (connListener != null) {
456                     connListener.onConnectionStateChange(ConnectionListener.APPLICATION_TOKEN_GENERATED);
457                 }
458             }
459         } else if (!isAuthenticated) {
460             if (connListener != null) {
461                 connListener.onConnectionStateChange(ConnectionListener.NOT_AUTHENTICATED,
462                         ConnectionListener.NO_USER_PASSWORD);
463             }
464         }
465     }
466
467     private boolean checkUserPassword() {
468         String userName = config.getUserName();
469         String password = config.getPassword();
470         return userName != null && !userName.isBlank() && password != null && !password.isBlank();
471     }
472
473     /**
474      * This method is called whenever the connection to the digitalSTROM-Server is lost.
475      *
476      * @param reason
477      */
478     private void onConnectionLost(String reason) {
479         if (connListener != null) {
480             connListener.onConnectionStateChange(ConnectionListener.CONNECTION_LOST, reason);
481         }
482         connectionEstablished = false;
483     }
484
485     /**
486      * This method is called whenever the connection to the digitalSTROM-Server is resumed.
487      */
488     private void onConnectionResumed() {
489         if (connListener != null) {
490             connListener.onConnectionStateChange(ConnectionListener.CONNECTION_RESUMED);
491         }
492         connectionEstablished = true;
493     }
494
495     @Override
496     public void registerConnectionListener(ConnectionListener listener) {
497         this.connListener = listener;
498     }
499
500     @Override
501     public void unregisterConnectionListener() {
502         this.connListener = null;
503     }
504
505     @Override
506     public String getApplicationToken() {
507         return config.getAppToken();
508     }
509
510     @Override
511     public boolean removeApplicationToken() {
512         String token = config.getAppToken();
513         if (token != null && !token.isBlank()) {
514             return digitalSTROMClient.revokeToken(token, null);
515         }
516         return true;
517     }
518
519     @Override
520     public void updateConfig(String host, String username, String password, String applicationToken) {
521         init(host, -1, -1, username, password, applicationToken, false);
522     }
523
524     @Override
525     public void updateConfig(Config config) {
526         if (this.config != null) {
527             this.config.updateConfig(config);
528         } else {
529             this.config = config;
530         }
531         init(this.config, false);
532     }
533
534     @Override
535     public void configHasBeenUpdated() {
536         init(this.config, false);
537     }
538
539     @Override
540     public Config getConfig() {
541         return this.config;
542     }
543 }