/**
* Copyright (c) 2010-2016 by the respective copyright holders.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.binding.withings.internal.api;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.lang.StringUtils;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import oauth.signpost.OAuthConsumer;
import oauth.signpost.OAuthProvider;
import oauth.signpost.basic.DefaultOAuthProvider;
import oauth.signpost.exception.OAuthException;
/**
* {@link WithingsAuthenticator} is responsible for authenticating openHAB
* against the Withings API. It uses the OSGi console to instruct the user how
* to execute the OAuth flow in the web browser. First the user needs to execute
* <code>withings:startAuthentication</code>. A URL for an OAuth login site is
* printed to the OSGi console. After login the user is redirected to a callback
* page where he finds the necessary parameters. Then he needs to execute
* <code>withings:finishAuthentication "<oauth-verifier>" "<user-id>"</code>
* to finish the authentication process. The {@link WithingsAuthenticator} will
* store the oauth tokens and the user id to the file system in the
* {@link WithingsAuthenticator#contentDir} folder.
*
* @see http://www.withings.com/de/api/oauthguide
*
* @author Dennis Nobel
* @author Thomas.Eichstaedt-Engelen
* @since 1.5.0
*/
public class WithingsAuthenticator implements ManagedService {
private static final Logger logger = LoggerFactory.getLogger(WithingsAuthenticator.class);
/** Default OAuth consumer key */
private static final String DEFAULT_CONSUMER_KEY = "74c0e77021ef5be1ec8dcb4dd88c15539f9541c86799dcbbfcb8fc8b236";
/** Default OAuth consumer secret */
private static final String DEFAULT_CONSUMER_SECRET = "25f1098263e511711b3287288f90740ff45532cef91658c5043db0b0e0c851c";
/** Default Redirect URL to which the user is redirected after the login */
private static final String DEFAULT_REDIRECT_URL = "http://www.openhab.org/oauth/withings";
private static final String LINE = "#########################################################################################";
private static final String OAUTH_ACCESS_TOKEN_ENDPOINT_URL = "https://oauth.withings.com/account/access_token";
private static final String OAUTH_AUTHORIZE_ENDPOINT_URL = "https://oauth.withings.com/account/authorize";
private static final String OAUTH_REQUEST_TOKEN_ENDPOINT = "https://oauth.withings.com/account/request_token";
static final String DEFAULT_ACCOUNT_ID = "DEFAULT_ACCOUNT_ID";
/** Redirect URL to which the user is redirected after the login */
private String redirectUrl = DEFAULT_REDIRECT_URL;
private String consumerKey = DEFAULT_CONSUMER_KEY;
private String consumerSecret = DEFAULT_CONSUMER_SECRET;
private OAuthProvider provider;
private ComponentContext componentContext;
private Map<String, WithingsAccount> accountsCache = new HashMap<String, WithingsAccount>();
protected void activate(ComponentContext componentContext) {
this.componentContext = componentContext;
}
protected void deactivate(ComponentContext componentContext) {
this.componentContext = null;
unregisterAccounts();
}
private WithingsAccount getAccount(String accountId) {
return accountsCache.get(accountId);
}
/**
* Starts the OAuth authentication flow.
*/
public synchronized void startAuthentication(String accountId) {
WithingsAccount withingsAccount = getAccount(accountId);
if (withingsAccount == null) {
logger.warn("Couldn't find Credentials of Account '{}'. Please check openhab.cfg or withings.cfg.",
accountId);
return;
}
OAuthConsumer consumer = withingsAccount.createConsumer();
provider = new DefaultOAuthProvider(OAUTH_REQUEST_TOKEN_ENDPOINT, OAUTH_ACCESS_TOKEN_ENDPOINT_URL,
OAUTH_AUTHORIZE_ENDPOINT_URL);
try {
String url = provider.retrieveRequestToken(consumer, this.redirectUrl);
printSetupInstructions(url);
} catch (OAuthException ex) {
logger.error(ex.getMessage(), ex);
printAuthenticationFailed(ex);
}
}
/**
* Finishes the OAuth authentication flow.
*
* @param verificationCode
* OAuth verification code
* @param userId
* user id
*/
public synchronized void finishAuthentication(String accountId, String verificationCode, String userId) {
WithingsAccount withingsAccount = getAccount(accountId);
if (withingsAccount == null) {
logger.warn("Couldn't find Credentials of Account '{}'. Please check openhab.cfg or withings.cfg.",
accountId);
return;
}
OAuthConsumer consumer = withingsAccount.consumer;
if (provider == null || consumer == null) {
logger.warn("Could not finish authentication. Please execute 'startAuthentication' first.");
return;
}
try {
provider.retrieveAccessToken(consumer, verificationCode);
} catch (OAuthException ex) {
logger.error(ex.getMessage(), ex);
printAuthenticationFailed(ex);
return;
}
withingsAccount.userId = userId;
withingsAccount.setOuathToken(consumer.getToken(), consumer.getTokenSecret());
withingsAccount.registerAccount(componentContext.getBundleContext());
withingsAccount.persist();
printAuthenticationSuccessful();
}
private void printSetupInstructions(String url) {
logger.info(LINE);
logger.info("# Withings Binding Setup: ");
logger.info("# 1. Open URL '" + url + "' in your web browser");
logger.info("# 2. Login, choose your user and allow openHAB to access your Withings data");
logger.info(
"# 3. Execute 'withings:finishAuthentication \"<accountId>\" \"<verifier>\" \"<userId>\"' on OSGi console");
logger.info(LINE);
}
private void printAuthenticationInfo(String accountId) {
logger.info(LINE);
logger.info("# Withings Binding needs authentication of Account '{}'.", accountId);
logger.info("# Execute 'withings:startAuthentication' \"<accountId>\" on OSGi console.");
logger.info(LINE);
}
private void printAuthenticationSuccessful() {
logger.info(LINE);
logger.info("# Withings authentication SUCCEEDED. Binding is now ready to work.");
logger.info(LINE);
}
private void printAuthenticationFailed(OAuthException ex) {
logger.info(LINE);
logger.info("# Withings authentication FAILED: " + ex.getMessage());
logger.info("# Try to restart authentication by executing 'withings:startAuthentication'");
logger.info(LINE);
}
@Override
public void updated(Dictionary<String, ?> config) throws ConfigurationException {
if (config != null) {
String redirectUrl = (String) config.get("redirectUrl");
if (StringUtils.isNotBlank(redirectUrl)) {
this.redirectUrl = redirectUrl;
}
String consumerKeyString = (String) config.get("consumerkey");
if (StringUtils.isNotBlank(consumerKeyString)) {
this.consumerKey = consumerKeyString;
}
String consumerSecretString = (String) config.get("consumersecret");
if (StringUtils.isNotBlank(consumerSecretString)) {
this.consumerSecret = consumerSecretString;
}
Enumeration<String> configKeys = config.keys();
while (configKeys.hasMoreElements()) {
String configKey = configKeys.nextElement();
// the config-key enumeration contains additional keys that we
// don't want to process here ...
if ("redirectUrl".equals(configKey) || "consumerkey".equals(configKey)
|| "consumersecret".equals(configKey) || "service.pid".equals(configKey)) {
continue;
}
String accountId;
String configKeyTail;
if (configKey.contains(".")) {
String[] keyElements = configKey.split("\\.");
accountId = keyElements[0];
configKeyTail = keyElements[1];
} else {
accountId = DEFAULT_ACCOUNT_ID;
configKeyTail = configKey;
}
WithingsAccount account = accountsCache.get(accountId);
if (account == null) {
account = new WithingsAccount(accountId, consumerKey, consumerSecret);
accountsCache.put(accountId, account);
}
String value = (String) config.get(configKeyTail);
if ("userid".equals(configKeyTail)) {
account.userId = value;
} else if ("token".equals(configKeyTail)) {
account.token = value;
} else if ("tokensecret".equals(configKeyTail)) {
account.tokenSecret = value;
} else {
throw new ConfigurationException(configKey, "The given configuration key is unknown!");
}
}
registerAccounts();
}
}
private void registerAccounts() {
for (Entry<String, WithingsAccount> entry : accountsCache.entrySet()) {
String accountId = entry.getKey();
WithingsAccount account = entry.getValue();
if (account.isAuthenticated()) {
account.registerAccount(componentContext.getBundleContext());
} else if (account.isValid()) {
printAuthenticationInfo(accountId);
} else {
logger.warn(
"Configuration details of Account '{}' are invalid please check openhab.cfg or withings.cfg.",
accountId);
}
}
}
private void unregisterAccounts() {
for (WithingsAccount account : accountsCache.values()) {
account.unregisterAccount();
}
}
}