]> git.basschouten.com Git - openhab-addons.git/blob
e4e4ca2a53ddc673a9ba2b8b3aaf06e92af4be27
[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.mielecloud.internal.config;
14
15 import java.io.IOException;
16 import java.time.LocalDateTime;
17 import java.util.concurrent.ScheduledExecutorService;
18 import java.util.concurrent.ScheduledFuture;
19 import java.util.concurrent.TimeUnit;
20
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.binding.mielecloud.internal.auth.OAuthException;
24 import org.openhab.binding.mielecloud.internal.config.exception.NoOngoingAuthorizationException;
25 import org.openhab.binding.mielecloud.internal.config.exception.OngoingAuthorizationException;
26 import org.openhab.binding.mielecloud.internal.webservice.DefaultMieleWebservice;
27 import org.openhab.core.auth.client.oauth2.AccessTokenResponse;
28 import org.openhab.core.auth.client.oauth2.OAuthClientService;
29 import org.openhab.core.auth.client.oauth2.OAuthFactory;
30 import org.openhab.core.auth.client.oauth2.OAuthResponseException;
31 import org.openhab.core.thing.ThingUID;
32
33 /**
34  * {@link OAuthAuthorizationHandler} implementation handling the OAuth 2 authorization via openHAB services.
35  *
36  * @author Björn Lange - Initial Contribution
37  */
38 @NonNullByDefault
39 public final class OAuthAuthorizationHandlerImpl implements OAuthAuthorizationHandler {
40     private static final String TOKEN_URL = DefaultMieleWebservice.THIRD_PARTY_ENDPOINTS_BASENAME + "/token";
41     private static final String AUTHORIZATION_URL = DefaultMieleWebservice.THIRD_PARTY_ENDPOINTS_BASENAME + "/login";
42
43     private static final long AUTHORIZATION_TIMEOUT_IN_MINUTES = 5;
44
45     private final OAuthFactory oauthFactory;
46     private final ScheduledExecutorService scheduler;
47
48     @Nullable
49     private OAuthClientService oauthClientService;
50     @Nullable
51     private ThingUID bridgeUid;
52     @Nullable
53     private String email;
54     @Nullable
55     private String redirectUri;
56     @Nullable
57     private ScheduledFuture<?> timer;
58     @Nullable
59     private LocalDateTime timerExpiryTimestamp;
60
61     /**
62      * Creates a new {@link OAuthAuthorizationHandlerImpl}.
63      *
64      * @param oauthFactory Factory for accessing the {@link OAuthClientService}.
65      * @param scheduler System-wide scheduler.
66      */
67     public OAuthAuthorizationHandlerImpl(OAuthFactory oauthFactory, ScheduledExecutorService scheduler) {
68         this.oauthFactory = oauthFactory;
69         this.scheduler = scheduler;
70     }
71
72     @Override
73     public synchronized void beginAuthorization(String clientId, String clientSecret, ThingUID bridgeUid,
74             String email) {
75         if (this.oauthClientService != null) {
76             throw new OngoingAuthorizationException("There is already an ongoing authorization!", timerExpiryTimestamp);
77         }
78
79         this.oauthClientService = oauthFactory.createOAuthClientService(email, TOKEN_URL, AUTHORIZATION_URL, clientId,
80                 clientSecret, null, false);
81         this.bridgeUid = bridgeUid;
82         this.email = email;
83         redirectUri = null;
84         timer = null;
85         timerExpiryTimestamp = null;
86     }
87
88     @Override
89     public synchronized String getAuthorizationUrl(String redirectUri) {
90         final OAuthClientService oauthClientService = this.oauthClientService;
91         if (oauthClientService == null) {
92             throw new NoOngoingAuthorizationException("There is no ongoing authorization!");
93         }
94
95         this.redirectUri = redirectUri;
96         try {
97             timer = scheduler.schedule(this::cancelAuthorization, AUTHORIZATION_TIMEOUT_IN_MINUTES, TimeUnit.MINUTES);
98             timerExpiryTimestamp = LocalDateTime.now().plusMinutes(AUTHORIZATION_TIMEOUT_IN_MINUTES);
99             return oauthClientService.getAuthorizationUrl(redirectUri, null, null);
100         } catch (org.openhab.core.auth.client.oauth2.OAuthException e) {
101             abortTimer();
102             cancelAuthorization();
103             throw new OAuthException("Failed to determine authorization URL: " + e.getMessage(), e);
104         }
105     }
106
107     @Override
108     public ThingUID getBridgeUid() {
109         final ThingUID bridgeUid = this.bridgeUid;
110         if (bridgeUid == null) {
111             throw new NoOngoingAuthorizationException("There is no ongoing authorization.");
112         }
113         return bridgeUid;
114     }
115
116     @Override
117     public String getEmail() {
118         final String email = this.email;
119         if (email == null) {
120             throw new NoOngoingAuthorizationException("There is no ongoing authorization.");
121         }
122         return email;
123     }
124
125     @Override
126     public synchronized void completeAuthorization(String redirectUrlWithParameters) {
127         abortTimer();
128
129         final OAuthClientService oauthClientService = this.oauthClientService;
130         if (oauthClientService == null) {
131             throw new NoOngoingAuthorizationException("There is no ongoing authorization.");
132         }
133
134         try {
135             String authorizationCode = oauthClientService.extractAuthCodeFromAuthResponse(redirectUrlWithParameters);
136
137             // Although this method is called "get" it actually fetches and stores the token response as a side effect.
138             oauthClientService.getAccessTokenResponseByAuthorizationCode(authorizationCode, redirectUri);
139         } catch (IOException e) {
140             throw new OAuthException("Network error while retrieving token response: " + e.getMessage(), e);
141         } catch (OAuthResponseException e) {
142             throw new OAuthException("Failed to retrieve token response: " + e.getMessage(), e);
143         } catch (org.openhab.core.auth.client.oauth2.OAuthException e) {
144             throw new OAuthException("Error while processing Miele service response: " + e.getMessage(), e);
145         } finally {
146             this.oauthClientService = null;
147             this.bridgeUid = null;
148             this.email = null;
149             this.redirectUri = null;
150         }
151     }
152
153     /**
154      * Aborts the timer.
155      *
156      * Note: All calls to this method must be {@code synchronized} to ensure thread-safety. Also note that
157      * {@link #cancelAuthorization()} is {@code synchronized} so the execution of this method and
158      * {@link #cancelAuthorization()} cannot overlap. Therefore, this method is an atomic operation from the timer's
159      * perspective.
160      */
161     private void abortTimer() {
162         final ScheduledFuture<?> timer = this.timer;
163         if (timer == null) {
164             return;
165         }
166
167         if (!timer.isDone()) {
168             timer.cancel(false);
169         }
170         this.timer = null;
171         timerExpiryTimestamp = null;
172     }
173
174     private synchronized void cancelAuthorization() {
175         oauthClientService = null;
176         bridgeUid = null;
177         email = null;
178         redirectUri = null;
179         final ScheduledFuture<?> timer = this.timer;
180         if (timer != null) {
181             timer.cancel(false);
182             this.timer = null;
183             timerExpiryTimestamp = null;
184         }
185     }
186
187     @Override
188     public String getAccessToken(String email) {
189         OAuthClientService clientService = oauthFactory.getOAuthClientService(email);
190         if (clientService == null) {
191             throw new OAuthException("There is no access token registered for '" + email + "'");
192         }
193
194         try {
195             AccessTokenResponse response = clientService.getAccessTokenResponse();
196             if (response == null) {
197                 throw new OAuthException(
198                         "There is no access token in the persistent storage or it already expired and could not be refreshed");
199             } else {
200                 return response.getAccessToken();
201             }
202         } catch (org.openhab.core.auth.client.oauth2.OAuthException e) {
203             throw new OAuthException("Failed to read access token from persistent storage: " + e.getMessage(), e);
204         } catch (IOException e) {
205             throw new OAuthException(
206                     "Network error during token refresh or error while reading from persistent storage: "
207                             + e.getMessage(),
208                     e);
209         } catch (OAuthResponseException e) {
210             throw new OAuthException("Failed to retrieve token response: " + e.getMessage(), e);
211         }
212     }
213 }