]> git.basschouten.com Git - openhab-addons.git/blob
090f9acab9f259cc0b6243f40f622b1df9fc1e8c
[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.windcentrale.internal.api;
14
15 import static org.eclipse.jetty.http.HttpHeader.ACCEPT;
16 import static org.eclipse.jetty.http.HttpMethod.GET;
17 import static org.openhab.binding.windcentrale.internal.dto.WindcentraleGson.GSON;
18
19 import java.time.Duration;
20 import java.time.Instant;
21 import java.util.Objects;
22 import java.util.concurrent.ExecutionException;
23 import java.util.concurrent.TimeUnit;
24 import java.util.concurrent.TimeoutException;
25
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.eclipse.jetty.client.api.ContentResponse;
29 import org.openhab.binding.windcentrale.internal.dto.AuthenticationResultResponse;
30 import org.openhab.binding.windcentrale.internal.dto.KeyResponse;
31 import org.openhab.binding.windcentrale.internal.exception.InvalidAccessTokenException;
32 import org.openhab.core.io.net.http.HttpClientFactory;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
35
36 /**
37  * Provides the JWT tokens used with the Windcentrale API by using a {@link AuthenticationHelper}.
38  * It also resolves the Windcentrale specific Cognito configuration required by the {@link AuthenticationHelper}.
39  *
40  * A token is obtained by calling {@link #getIdToken()}.
41  * The token is cached and returned in subsequent calls to {@link #getIdToken()} until it expires.
42  * When tokens expire they are refreshed using the refresh token when available.
43  *
44  * @author Wouter Born - Initial contribution
45  */
46 @NonNullByDefault
47 public class TokenProvider {
48
49     private final Logger logger = LoggerFactory.getLogger(TokenProvider.class);
50
51     private static final String DEFAULT_USER_POOL_ID = "eu-west-1_U7eYBPrBd";
52     private static final String DEFAULT_CLIENT_ID = "715j3r0trk7o8dqg3md57il7q0";
53     private static final String DEFAULT_REGION = "eu-west-1";
54
55     private static final String APPLICATION_JSON = "application/json";
56     private static final Duration REQUEST_TIMEOUT = Duration.ofMinutes(1);
57     private static final String KEY_URL = WindcentraleAPI.URL_PREFIX + "/labels/key?domain=mijn.windcentrale.nl";
58
59     private final HttpClientFactory httpClientFactory;
60
61     private final String username;
62     private final String password;
63
64     private @Nullable AuthenticationHelper authenticationHelper;
65
66     private String idToken = "";
67     private String refreshToken = "";
68     private Instant validityEnd = Instant.MIN;
69
70     public TokenProvider(HttpClientFactory httpClientFactory, String username, String password) {
71         this.httpClientFactory = httpClientFactory;
72         this.username = username;
73         this.password = password;
74     }
75
76     private AuthenticationHelper createHelper() {
77         String userPoolId = DEFAULT_USER_POOL_ID;
78         String clientId = DEFAULT_CLIENT_ID;
79         String region = DEFAULT_REGION;
80
81         try {
82             logger.debug("Getting JSON from: {}", KEY_URL);
83             ContentResponse contentResponse = httpClientFactory.getCommonHttpClient().newRequest(KEY_URL) //
84                     .method(GET) //
85                     .header(ACCEPT, APPLICATION_JSON) //
86                     .timeout(REQUEST_TIMEOUT.toNanos(), TimeUnit.NANOSECONDS) //
87                     .send();
88
89             String response = contentResponse.getContentAsString();
90             if (contentResponse.getStatus() >= 400) {
91                 logger.debug("Could not get Cognito configuration values, using default values. Error (HTTP {}): {}",
92                         contentResponse.getStatus(), contentResponse.getReason());
93             } else {
94                 logger.trace("Response: {}", response);
95                 KeyResponse keyResponse = Objects.requireNonNullElse(GSON.fromJson(response, KeyResponse.class),
96                         new KeyResponse());
97                 if (!keyResponse.userPoolId.isEmpty() && !keyResponse.clientId.isEmpty()
98                         && keyResponse.region.isEmpty()) {
99                     userPoolId = keyResponse.userPoolId;
100                     clientId = keyResponse.clientId;
101                     region = keyResponse.region;
102                 }
103             }
104         } catch (ExecutionException | InterruptedException | TimeoutException e) {
105             logger.debug("Could not get Cognito configuration values, using default values", e);
106         }
107
108         logger.debug("Creating new AuthenticationHelper (userPoolId={}, clientId={}, region={})", userPoolId, clientId,
109                 region);
110         return new AuthenticationHelper(httpClientFactory, userPoolId, clientId, region);
111     }
112
113     private AuthenticationHelper getOrCreateHelper() {
114         AuthenticationHelper helper = authenticationHelper;
115         if (helper == null) {
116             helper = createHelper();
117             this.authenticationHelper = helper;
118         }
119         return helper;
120     }
121
122     public String getIdToken() throws InvalidAccessTokenException {
123         boolean valid = Instant.now().plusSeconds(30).isBefore(validityEnd);
124         if (valid) {
125             logger.debug("Reusing existing valid token");
126             return idToken;
127         }
128
129         AuthenticationResultResponse result = null;
130         AuthenticationHelper helper = getOrCreateHelper();
131
132         if (!refreshToken.isBlank()) {
133             try {
134                 logger.debug("Performing token refresh");
135                 result = helper.performTokenRefresh(refreshToken);
136                 logger.debug("Successfully performed token refresh");
137             } catch (InvalidAccessTokenException e) {
138                 logger.debug("Token refresh failed", e);
139             }
140         }
141
142         if (result == null) {
143             // there is no refresh token or the refresh failed
144             logger.debug("Performing SRP authentication");
145             result = helper.performSrpAuthentication(username, password);
146             logger.debug("Successfully performed SRP authentication");
147
148             refreshToken = result.getRefreshToken();
149         }
150
151         idToken = result.getIdToken();
152         validityEnd = Instant.now().plusSeconds(result.getExpiresIn());
153         logger.debug("Token is valid until {}", validityEnd);
154         return idToken;
155     }
156 }