2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.mielecloud.internal.config;
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;
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;
34 * {@link OAuthAuthorizationHandler} implementation handling the OAuth 2 authorization via openHAB services.
36 * @author Björn Lange - Initial Contribution
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";
43 private static final long AUTHORIZATION_TIMEOUT_IN_MINUTES = 5;
45 private final OAuthFactory oauthFactory;
46 private final ScheduledExecutorService scheduler;
49 private OAuthClientService oauthClientService;
51 private ThingUID bridgeUid;
55 private String redirectUri;
57 private ScheduledFuture<?> timer;
59 private LocalDateTime timerExpiryTimestamp;
62 * Creates a new {@link OAuthAuthorizationHandlerImpl}.
64 * @param oauthFactory Factory for accessing the {@link OAuthClientService}.
65 * @param scheduler System-wide scheduler.
67 public OAuthAuthorizationHandlerImpl(OAuthFactory oauthFactory, ScheduledExecutorService scheduler) {
68 this.oauthFactory = oauthFactory;
69 this.scheduler = scheduler;
73 public synchronized void beginAuthorization(String clientId, String clientSecret, ThingUID bridgeUid,
75 if (this.oauthClientService != null) {
76 throw new OngoingAuthorizationException("There is already an ongoing authorization!", timerExpiryTimestamp);
79 this.oauthClientService = oauthFactory.createOAuthClientService(email, TOKEN_URL, AUTHORIZATION_URL, clientId,
80 clientSecret, null, false);
81 this.bridgeUid = bridgeUid;
85 timerExpiryTimestamp = null;
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!");
95 this.redirectUri = redirectUri;
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) {
102 cancelAuthorization();
103 throw new OAuthException("Failed to determine authorization URL: " + e.getMessage(), e);
108 public ThingUID getBridgeUid() {
109 final ThingUID bridgeUid = this.bridgeUid;
110 if (bridgeUid == null) {
111 throw new NoOngoingAuthorizationException("There is no ongoing authorization.");
117 public String getEmail() {
118 final String email = this.email;
120 throw new NoOngoingAuthorizationException("There is no ongoing authorization.");
126 public synchronized void completeAuthorization(String redirectUrlWithParameters) {
129 final OAuthClientService oauthClientService = this.oauthClientService;
130 if (oauthClientService == null) {
131 throw new NoOngoingAuthorizationException("There is no ongoing authorization.");
135 String authorizationCode = oauthClientService.extractAuthCodeFromAuthResponse(redirectUrlWithParameters);
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);
146 this.oauthClientService = null;
147 this.bridgeUid = null;
149 this.redirectUri = null;
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
161 private void abortTimer() {
162 final ScheduledFuture<?> timer = this.timer;
167 if (!timer.isDone()) {
171 timerExpiryTimestamp = null;
174 private synchronized void cancelAuthorization() {
175 oauthClientService = null;
179 final ScheduledFuture<?> timer = this.timer;
183 timerExpiryTimestamp = null;
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 + "'");
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");
200 return response.getAccessToken();
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: "
209 } catch (OAuthResponseException e) {
210 throw new OAuthException("Failed to retrieve token response: " + e.getMessage(), e);