2 * Copyright (c) 2010-2022 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.netatmo.internal.api;
15 import static org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.PATH_OAUTH;
16 import static org.openhab.core.auth.oauth2client.internal.Keyword.*;
19 import java.util.HashMap;
21 import java.util.Optional;
23 import java.util.concurrent.ScheduledExecutorService;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.FeatureArea;
30 import org.openhab.binding.netatmo.internal.api.data.NetatmoConstants.Scope;
31 import org.openhab.binding.netatmo.internal.api.dto.AccessTokenResponse;
32 import org.openhab.binding.netatmo.internal.config.ApiHandlerConfiguration.Credentials;
33 import org.openhab.binding.netatmo.internal.handler.ApiBridgeHandler;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
38 * The {@link AuthenticationApi} handles oAuth2 authentication and token refreshing
40 * @author Gaƫl L'hopital - Initial contribution
43 public class AuthenticationApi extends RestManager {
44 private static final URI OAUTH_URI = getApiBaseBuilder().path(PATH_OAUTH).build();
46 private final ScheduledExecutorService scheduler;
47 private final Logger logger = LoggerFactory.getLogger(AuthenticationApi.class);
49 private @Nullable ScheduledFuture<?> refreshTokenJob;
50 private Optional<AccessTokenResponse> tokenResponse = Optional.empty();
51 private String scope = "";
53 public AuthenticationApi(ApiBridgeHandler bridge, ScheduledExecutorService scheduler) {
54 super(bridge, FeatureArea.NONE);
55 this.scheduler = scheduler;
58 public void authenticate(Credentials credentials, Set<FeatureArea> features) throws NetatmoException {
59 Set<FeatureArea> requestedFeatures = !features.isEmpty() ? features : FeatureArea.AS_SET;
60 scope = FeatureArea.toScopeString(requestedFeatures);
61 requestToken(credentials.clientId, credentials.clientSecret,
62 Map.of(USERNAME, credentials.username, PASSWORD, credentials.password, SCOPE, scope));
65 private void requestToken(String id, String secret, Map<String, String> entries) throws NetatmoException {
66 Map<String, String> payload = new HashMap<>(entries);
67 payload.putAll(Map.of(GRANT_TYPE, entries.keySet().contains(PASSWORD) ? PASSWORD : REFRESH_TOKEN, CLIENT_ID, id,
68 CLIENT_SECRET, secret));
70 AccessTokenResponse response = post(OAUTH_URI, AccessTokenResponse.class, payload);
71 refreshTokenJob = scheduler.schedule(() -> {
73 requestToken(id, secret, Map.of(REFRESH_TOKEN, response.getRefreshToken()));
74 } catch (NetatmoException e) {
75 logger.warn("Unable to refresh access token : {}", e.getMessage());
77 }, Math.round(response.getExpiresIn() * 0.8), TimeUnit.SECONDS);
78 tokenResponse = Optional.of(response);
81 public void disconnect() {
82 tokenResponse = Optional.empty();
85 public void dispose() {
86 ScheduledFuture<?> job = refreshTokenJob;
90 refreshTokenJob = null;
93 public @Nullable String getAuthorization() {
94 return tokenResponse.map(at -> String.format("Bearer %s", at.getAccessToken())).orElse(null);
97 public boolean matchesScopes(Set<Scope> requiredScopes) {
98 // either we do not require any scope, either connected and all scopes available
99 return requiredScopes.isEmpty()
100 || (isConnected() && tokenResponse.map(at -> at.getScope().containsAll(requiredScopes)).orElse(false));
103 public boolean isConnected() {
104 return !tokenResponse.isEmpty();