]> git.basschouten.com Git - openhab-addons.git/blob
ad397c564696c761eaf952196f496d0507a2679d
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.apache.commons.lang.StringUtils;
18 import org.openhab.binding.digitalstrom.internal.lib.config.Config;
19 import org.openhab.binding.digitalstrom.internal.lib.listener.ConnectionListener;
20 import org.openhab.binding.digitalstrom.internal.lib.manager.ConnectionManager;
21 import org.openhab.binding.digitalstrom.internal.lib.serverconnection.DsAPI;
22 import org.openhab.binding.digitalstrom.internal.lib.serverconnection.HttpTransport;
23 import org.openhab.binding.digitalstrom.internal.lib.serverconnection.impl.DsAPIImpl;
24 import org.openhab.binding.digitalstrom.internal.lib.serverconnection.impl.HttpTransportImpl;
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
27
28 import com.google.gson.JsonArray;
29 import com.google.gson.JsonObject;
30
31 /**
32  * The {@link ConnectionManagerImpl} is the implementation of the {@link ConnectionManager}.
33  *
34  * @author Michael Ochel - Initial contribution
35  * @author Matthias Siegele - Initial contribution
36  */
37 public class ConnectionManagerImpl implements ConnectionManager {
38
39     /**
40      * Query to get all enabled application tokens. Can be executed with {@link DsAPI#query(String, String)} or
41      * {@link DsAPI#query2(String, String)}.
42      */
43     public final String QUERY_GET_ENABLED_APPLICATION_TOKENS = "/system/security/applicationTokens/enabled/*(*)";
44     private final Logger logger = LoggerFactory.getLogger(ConnectionManagerImpl.class);
45
46     private Config config;
47     private ConnectionListener connListener;
48     private HttpTransport transport;
49     private String sessionToken;
50     private Boolean connectionEstablished = false;
51     private boolean genAppToken;
52     private DsAPI digitalSTROMClient;
53
54     /**
55      * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String)}, but the connection
56      * timeout and read timeout can be set, too.
57      *
58      * @param hostArddress (must not be null)
59      * @param connectTimeout (if connectTimeout is lower than 0 the {@link Config#DEFAULT_CONNECTION_TIMEOUT} will be
60      *            set)
61      * @param readTimeout (if readTimeout is lower than 0 the {@link Config#DEFAULT_CONNECTION_TIMEOUT} will be set)
62      * @param username (can be null, if application token is set)
63      * @param password (can be null, if application token is set
64      * @param applicationToken (can be null, if username and password is set)
65      * @see #ConnectionManagerImpl(String, String, String, String)
66      */
67     public ConnectionManagerImpl(String hostArddress, int connectTimeout, int readTimeout, String username,
68             String password, String applicationToken) {
69         init(hostArddress, connectTimeout, readTimeout, username, password, applicationToken, false);
70     }
71
72     /**
73      * Creates a new {@link ConnectionManagerImpl} through a {@link Config} object, which has all configurations set.
74      *
75      * @param config (must not be null)
76      */
77     public ConnectionManagerImpl(Config config) {
78         init(config, false);
79     }
80
81     /**
82      * The same constructor like {@link #ConnectionManagerImpl(Config)}, but a {@link ConnectionListener} can be
83      * registered, too.
84      *
85      * @param config (must not be null)
86      * @param connectionListener (can be null)
87      * @see #ConnectionManagerImpl(Config)
88      */
89     public ConnectionManagerImpl(Config config, ConnectionListener connectionListener) {
90         this.connListener = connectionListener;
91         init(config, false);
92     }
93
94     /**
95      * The same constructor like {@link #ConnectionManagerImpl(Config, ConnectionListener)}, but through genApToken it
96      * can be set, if a application token will be automatically generated.
97      *
98      * @param config (must not be null)
99      * @param connectionListener (can be null)
100      * @param genAppToken (true = application token will be generated, otherwise false)
101      * @see #ConnectionManagerImpl(Config, ConnectionListener)
102      */
103     public ConnectionManagerImpl(Config config, ConnectionListener connectionListener, boolean genAppToken) {
104         this.connListener = connectionListener;
105         this.genAppToken = genAppToken;
106         init(config, false);
107     }
108
109     /**
110      * Creates a new {@link ConnectionManagerImpl} with the given parameters, which are needed to create the
111      * {@link HttpTransport} and to login into the digitalSTROM server. If the application token is null and the
112      * username and password are valid, a application token will be automatically generated or a existing application
113      * token for the at {@link Config#getApplicationName()} set application name will be set.
114      *
115      * @param hostAddress (must not be null)
116      * @param username (can be null, if application token is set)
117      * @param password (can be null, if application token is set
118      * @param applicationToken (can be null, if username and password is set)
119      */
120     public ConnectionManagerImpl(String hostAddress, String username, String password, String applicationToken) {
121         init(hostAddress, -1, -1, username, password, applicationToken, false);
122     }
123
124     /**
125      * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String)}, but without username
126      * and password.
127      *
128      * @param hostAddress (must not be null)
129      * @param applicationToken (must not be null)
130      */
131     public ConnectionManagerImpl(String hostAddress, String applicationToken) {
132         init(hostAddress, -1, -1, null, null, applicationToken, false);
133     }
134
135     /**
136      * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String)}, but without application
137      * token.
138      *
139      * @param hostAddress (must not be null)
140      * @param username (must not be null)
141      * @param password (must not be null)
142      * @see #ConnectionManagerImpl(String, String, String, String)
143      */
144     public ConnectionManagerImpl(String hostAddress, String username, String password) {
145         init(hostAddress, -1, -1, username, password, null, false);
146     }
147
148     /**
149      * The same constructor like {@link #ConnectionManagerImpl(String, String, String)}, but a
150      * {@link ConnectionListener} can be set, too.
151      *
152      * @param hostAddress (must not be null)
153      * @param username (must not be null)
154      * @param password (must not be null)
155      * @param connectionListener (can be null)
156      * @see #ConnectionManagerImpl(String, String, String)
157      */
158     public ConnectionManagerImpl(String hostAddress, String username, String password,
159             ConnectionListener connectionListener) {
160         this.connListener = connectionListener;
161         init(hostAddress, -1, -1, username, password, null, false);
162     }
163
164     /**
165      * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String)}, but a
166      * {@link ConnectionListener} can be set, too.
167      *
168      * @param hostAddress (must not be null)
169      * @param username (can be null, if application token is set)
170      * @param password (can be null, if application token is set)
171      * @param applicationToken (can be null, if username and password is set)
172      * @param connectionListener (can be null)
173      * @see #ConnectionManagerImpl(String, String, String, String)
174      */
175     public ConnectionManagerImpl(String hostAddress, String username, String password, String applicationToken,
176             ConnectionListener connectionListener) {
177         this.connListener = connectionListener;
178         init(hostAddress, -1, -1, username, password, null, false);
179     }
180
181     /**
182      * The same constructor like {@link #ConnectionManagerImpl(String, String, String)}, but through genApToken it
183      * can be set, if a application token will be automatically generated.
184      *
185      * @param hostAddress (must not be null)
186      * @param username (must not be null)
187      * @param password (must not be null)
188      * @param genAppToken (true = application token will be generated, otherwise false)
189      * @see #ConnectionManagerImpl(String, String, String, String)
190      */
191     public ConnectionManagerImpl(String hostAddress, String username, String password, boolean genAppToken) {
192         this.genAppToken = genAppToken;
193         init(hostAddress, -1, -1, username, password, null, false);
194     }
195
196     /**
197      * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String)}, but through genApToken
198      * it can be set, if a application token will be automatically generated.
199      *
200      * @param hostAddress (must not be null)
201      * @param username (can be null, if application token is set)
202      * @param password (can be null, if application token is set)
203      * @param applicationToken (can be null, if username and password is set)
204      * @param genAppToken (true = application token will be generated, otherwise false)
205      * @see #ConnectionManagerImpl(String, String, String, String)
206      */
207     public ConnectionManagerImpl(String hostAddress, String username, String password, String applicationToken,
208             boolean genAppToken) {
209         this.genAppToken = genAppToken;
210         init(hostAddress, -1, -1, username, password, applicationToken, false);
211     }
212
213     /**
214      * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String, boolean)}, but through
215      * acceptAllCerts it can be set, if all SSL-Certificates will be accept.
216      *
217      * @param hostAddress (must not be null)
218      * @param username (can be null, if application token is set)
219      * @param password (can be null, if application token is set)
220      * @param applicationToken (can be null, if username and password is set)
221      * @param genAppToken (true = application token will be generated, otherwise false)
222      * @param acceptAllCerts (true = all SSL-Certificates will be accept, otherwise false)
223      * @see #ConnectionManagerImpl(String, String, String, String, boolean)
224      */
225     public ConnectionManagerImpl(String hostAddress, String username, String password, String applicationToken,
226             boolean genAppToken, boolean acceptAllCerts) {
227         this.genAppToken = genAppToken;
228         init(hostAddress, -1, -1, username, password, applicationToken, acceptAllCerts);
229     }
230
231     /**
232      * The same constructor like {@link #ConnectionManagerImpl(String, String, String, String, boolean)}, but a
233      * {@link ConnectionListener} can be set, too.
234      *
235      * @param hostAddress (must not be null)
236      * @param username (can be null, if application token is set)
237      * @param password (can be null, if application token is set)
238      * @param applicationToken (can be null, if username and password is set)
239      * @param genAppToken (true = application token will be generated, otherwise false)
240      * @param connectionListener (can be null)
241      * @see #ConnectionManagerImpl(String, String, String, String, boolean)
242      */
243     public ConnectionManagerImpl(String hostAddress, String username, String password, String applicationToken,
244             boolean genAppToken, ConnectionListener connectionListener) {
245         this.connListener = connectionListener;
246         this.genAppToken = genAppToken;
247         init(hostAddress, -1, -1, username, password, applicationToken, false);
248     }
249
250     private void init(String hostAddress, int connectionTimeout, int readTimeout, String username, String password,
251             String applicationToken, boolean acceptAllCerts) {
252         config = new Config(hostAddress, username, password, applicationToken);
253         if (connectionTimeout >= 0) {
254             config.setConnectionTimeout(connectionTimeout);
255         }
256         if (readTimeout >= 0) {
257             config.setReadTimeout(readTimeout);
258         }
259         init(config, acceptAllCerts);
260     }
261
262     private void init(Config config, boolean acceptAllCerts) {
263         this.config = config;
264         this.transport = new HttpTransportImpl(this, acceptAllCerts);
265         this.digitalSTROMClient = new DsAPIImpl(transport);
266         if (this.genAppToken) {
267             this.onNotAuthenticated();
268         }
269     }
270
271     @Override
272     public HttpTransport getHttpTransport() {
273         return transport;
274     }
275
276     @Override
277     public DsAPI getDigitalSTROMAPI() {
278         return this.digitalSTROMClient;
279     }
280
281     @Override
282     public String getSessionToken() {
283         return this.sessionToken;
284     }
285
286     @Override
287     public String getNewSessionToken() {
288         if (this.genAppToken) {
289             if (StringUtils.isNotBlank(config.getAppToken())) {
290                 sessionToken = this.digitalSTROMClient.loginApplication(config.getAppToken());
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         if (StringUtils.isNotBlank(config.getAppToken())) {
383             sessionToken = digitalSTROMClient.loginApplication(config.getAppToken());
384             if (sessionToken != null) {
385                 isAuthenticated = true;
386             } else {
387                 if (connListener != null) {
388                     connListener.onConnectionStateChange(ConnectionListener.NOT_AUTHENTICATED,
389                             ConnectionListener.WRONG_APP_TOKEN);
390                     if (!checkUserPassword()) {
391                         return;
392                     }
393                 }
394             }
395         }
396         if (checkUserPassword()) {
397             if (!isAuthenticated) {
398                 // if an application-token for the application exists, use this application-token and test host is
399                 // reachable
400                 logger.debug("check existing application-tokens");
401                 sessionToken = digitalSTROMClient.login(config.getUserName(), config.getPassword());
402                 if (sessionToken != null) {
403                     JsonObject jObj = digitalSTROMClient.query(sessionToken, QUERY_GET_ENABLED_APPLICATION_TOKENS);
404
405                     if (jObj != null) {
406                         if (jObj.get("enabled") != null && jObj.get("enabled").isJsonArray()) {
407                             JsonArray jArray = jObj.get("enabled").getAsJsonArray();
408                             // application-token check
409                             for (int i = 0; i < jArray.size(); i++) {
410                                 JsonObject appToken = jArray.get(i).getAsJsonObject();
411                                 if (appToken.get("applicationName") != null && appToken.get("applicationName")
412                                         .getAsString().equals(config.getApplicationName())) {
413                                     // found application-token, set as application-token
414                                     applicationToken = appToken.get("token").getAsString();
415                                     logger.debug("found application-token {} for application {}", applicationToken,
416                                             config.getApplicationName());
417                                     break;
418                                 }
419                             }
420                         }
421                         if (applicationToken == null) {
422                             // no token found, generate applicationToken
423                             applicationToken = this.digitalSTROMClient
424                                     .requestAppplicationToken(config.getApplicationName());
425                             logger.debug(
426                                     "no application-token for application {} found, generate a application-token {}",
427                                     config.getApplicationName(), applicationToken);
428                             if (StringUtils.isNotBlank(applicationToken)) {
429                                 // enable applicationToken
430                                 if (!digitalSTROMClient.enableApplicationToken(applicationToken,
431                                         digitalSTROMClient.login(config.getUserName(), config.getPassword()))) {
432                                     // if enable failed set application-token = null so thats not will be set
433                                     applicationToken = null;
434                                 }
435                             }
436                         }
437                         if (applicationToken != null) {
438                             logger.debug("application-token can be used");
439                             config.setAppToken(applicationToken);
440                             isAuthenticated = true;
441                         }
442                     }
443                 } else {
444                     if (connListener != null) {
445                         connListener.onConnectionStateChange(ConnectionListener.NOT_AUTHENTICATED,
446                                 ConnectionListener.WRONG_USER_OR_PASSWORD);
447                         return;
448                     }
449                 }
450             }
451             // remove password and username, to don't store them persistently
452             if (isAuthenticated) {
453                 config.removeUsernameAndPassword();
454                 if (connListener != null) {
455                     connListener.onConnectionStateChange(ConnectionListener.APPLICATION_TOKEN_GENERATED);
456                 }
457             }
458         } else if (!isAuthenticated) {
459             if (connListener != null) {
460                 connListener.onConnectionStateChange(ConnectionListener.NOT_AUTHENTICATED,
461                         ConnectionListener.NO_USER_PASSWORD);
462             }
463         }
464     }
465
466     private boolean checkUserPassword() {
467         if (StringUtils.isNotBlank(config.getUserName()) && StringUtils.isNotBlank(config.getPassword())) {
468             return true;
469         }
470         return false;
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         if (StringUtils.isNotBlank(config.getAppToken())) {
513             return digitalSTROMClient.revokeToken(config.getAppToken(), null);
514         }
515         return true;
516     }
517
518     @Override
519     public void updateConfig(String host, String username, String password, String applicationToken) {
520         init(host, -1, -1, username, password, applicationToken, false);
521     }
522
523     @Override
524     public void updateConfig(Config config) {
525         if (this.config != null) {
526             this.config.updateConfig(config);
527         } else {
528             this.config = config;
529         }
530         init(this.config, false);
531     }
532
533     @Override
534     public void configHasBeenUpdated() {
535         init(this.config, false);
536     }
537
538     @Override
539     public Config getConfig() {
540         return this.config;
541     }
542 }