/** * 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.io.gcal.auth; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.api.client.auth.oauth2.BearerToken; import com.google.api.client.auth.oauth2.ClientParametersAuthentication; import com.google.api.client.auth.oauth2.Credential; import com.google.api.client.auth.oauth2.DataStoreCredentialRefreshListener; import com.google.api.client.auth.oauth2.StoredCredential; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpRequestFactory; import com.google.api.client.http.HttpRequestInitializer; import com.google.api.client.http.HttpTransport; import com.google.api.client.http.UrlEncodedContent; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.JsonObjectParser; import com.google.api.client.json.jackson2.JacksonFactory; import com.google.api.client.util.Clock; import com.google.api.client.util.Key; import com.google.api.client.util.store.DataStore; import com.google.api.client.util.store.FileDataStoreFactory; import com.google.api.services.calendar.Calendar; import com.google.api.services.calendar.CalendarScopes; import com.google.api.services.calendar.model.CalendarList; import com.google.api.services.calendar.model.CalendarListEntry; /** * Service which downloads Calendar events, parses their content and creates * Quartz-jobs and triggers out of them. * * @author Adam Pienczykowski * @since 1.9.0 */ public class GCalGoogleOAuth { private static final Logger logger = LoggerFactory.getLogger(GCalGoogleOAuth.class); private static final String TOKEN_PATH = "gcal"; private static final String TOKEN_STORE_USER_ID = "openhab"; private static String client_id = ""; private static String client_secret = ""; private static CalendarList calendarList = null; public static class Device { @Key public String device_code; @Key public String user_code; @Key public String verification_url; @Key public int expires_in; @Key public int interval; } public static class DeviceToken { @Key public String access_token; @Key public String token_type; @Key public String refresh_token; @Key public int expires_in; } private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport(); /** * Define a global instance of the JSON factory. */ private static final JsonFactory JSON_FACTORY = new JacksonFactory(); /** * Setup OAuth2 client_id used for authentication */ public static void setClientId(String id) { client_id = id; } /** * Setup OAuth2 client_secret used for authentication */ public static void setClientSecret(String secret) { client_secret = secret; } /** * <p> * Perform OAuth2 authorization with Google server based on provided client_id and client_secret and * stores credential in local persistent store. * </p> * * @param newCredential If true try to obtain new credential (user interaction required) * @return Authorization credential object. */ public static Credential getCredential(boolean newCredential) { Credential credential = null; try { File tokenPath = null; String userdata = System.getProperty("smarthome.userdata"); if (StringUtils.isEmpty(userdata)) { tokenPath = new File("etc"); } else { tokenPath = new File(userdata); } File tokenFile = new File(tokenPath, TOKEN_PATH); FileDataStoreFactory fileDataStoreFactory = new FileDataStoreFactory(tokenFile); DataStore<StoredCredential> datastore = fileDataStoreFactory.getDataStore("gcal_oauth2_token"); credential = loadCredential("openhab", datastore); if (credential == null && newCredential) { if (StringUtils.isBlank(client_id) || StringUtils.isBlank(client_secret)) { logger.warn("OAuth2 credentials are not provided"); return null; } GenericUrl genericUrl = new GenericUrl("https://accounts.google.com/o/oauth2/device/code"); Map<String, String> mapData = new HashMap<String, String>(); mapData.put("client_id", client_id); mapData.put("scope", CalendarScopes.CALENDAR); UrlEncodedContent content = new UrlEncodedContent(mapData); HttpRequestFactory requestFactory = HTTP_TRANSPORT.createRequestFactory(new HttpRequestInitializer() { @Override public void initialize(HttpRequest request) { request.setParser(new JsonObjectParser(JSON_FACTORY)); } }); HttpRequest postRequest = requestFactory.buildPostRequest(genericUrl, content); Device device = postRequest.execute().parseAs(Device.class); // no access token/secret specified so display the authorisation URL in the log logger.info( "################################################################################################"); logger.info("# Google-Integration: U S E R I N T E R A C T I O N R E Q U I R E D !!"); logger.info("# 1. Open URL '{}'", device.verification_url); logger.info("# 2. Type provided code {} ", device.user_code); logger.info("# 3. Grant openHAB access to your Google calendar"); logger.info( "# 4. openHAB will automatically detect the permiossions and complete the authentication process"); logger.info("# NOTE: You will only have {} mins before openHAB gives up waiting for the access!!!", device.expires_in); logger.info( "################################################################################################"); if (logger.isDebugEnabled()) { logger.debug("Got access code"); logger.debug("user code : {}", device.user_code); logger.debug("device code : {}", device.device_code); logger.debug("expires in: {}", device.expires_in); logger.debug("interval : {}", device.interval); logger.debug("verification_url : {}", device.verification_url); } mapData = new HashMap<String, String>(); mapData.put("client_id", client_id); mapData.put("client_secret", client_secret); mapData.put("code", device.device_code); mapData.put("grant_type", "http://oauth.net/grant_type/device/1.0"); content = new UrlEncodedContent(mapData); postRequest = requestFactory .buildPostRequest(new GenericUrl("https://accounts.google.com/o/oauth2/token"), content); DeviceToken deviceToken; do { deviceToken = postRequest.execute().parseAs(DeviceToken.class); if (deviceToken.access_token != null) { if (logger.isDebugEnabled()) { logger.debug("Got access token"); logger.debug("device access token: {}", deviceToken.access_token); logger.debug("device token_type: {}", deviceToken.token_type); logger.debug("device refresh_token: {}", deviceToken.refresh_token); logger.debug("device expires_in: {}", deviceToken.expires_in); } break; } logger.debug("waiting for {} seconds", device.interval); Thread.sleep(device.interval * 1000); } while (true); StoredCredential dataCredential = new StoredCredential(); dataCredential.setAccessToken(deviceToken.access_token); dataCredential.setRefreshToken(deviceToken.refresh_token); dataCredential.setExpirationTimeMilliseconds((long) deviceToken.expires_in * 1000); datastore.set(TOKEN_STORE_USER_ID, dataCredential); credential = loadCredential(TOKEN_STORE_USER_ID, datastore); } } catch (Exception e) { logger.warn("getCredential got exception: {}", e.getMessage()); } return credential; } private static Credential loadCredential(String userId, DataStore<StoredCredential> credentialDataStore) throws IOException { Credential credential = newCredential(userId, credentialDataStore); if (credentialDataStore != null) { StoredCredential stored = credentialDataStore.get(userId); if (stored == null) { return null; } credential.setAccessToken(stored.getAccessToken()); credential.setRefreshToken(stored.getRefreshToken()); credential.setExpirationTimeMilliseconds(stored.getExpirationTimeMilliseconds()); if (logger.isDebugEnabled()) { logger.debug("Loaded credential"); logger.debug("device access token: {}", stored.getAccessToken()); logger.debug("device refresh_token: {}", stored.getRefreshToken()); logger.debug("device expires_in: {}", stored.getExpirationTimeMilliseconds()); } } return credential; } private static Credential newCredential(String userId, DataStore<StoredCredential> credentialDataStore) { Credential.Builder builder = new Credential.Builder(BearerToken.authorizationHeaderAccessMethod()) .setTransport(HTTP_TRANSPORT).setJsonFactory(JSON_FACTORY) .setTokenServerEncodedUrl("https://accounts.google.com/o/oauth2/token") .setClientAuthentication(new ClientParametersAuthentication(client_id, client_secret)) .setRequestInitializer(null).setClock(Clock.SYSTEM); builder.addRefreshListener(new DataStoreCredentialRefreshListener(userId, credentialDataStore)); return builder.build(); } /** * Return calendarID based on calendar name. * if calendar name is "primary" not check primary - just return primary * * @param calendar object */ public static CalendarListEntry getCalendarId(String calendar_name) { CalendarListEntry calendarID = null; if (calendarList == null) { Calendar client = new Calendar.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredential(false)) .setApplicationName("openHAB").build(); try { calendarList = client.calendarList().list().execute(); } catch (com.google.api.client.auth.oauth2.TokenResponseException ae) { logger.error("authentication failed: {}", ae.getMessage()); return null; } catch (IOException e1) { logger.error("authentication I/O exception: {}", e1.getMessage()); return null; } } if (calendarList != null && calendarList.getItems() != null) { for (CalendarListEntry entry : calendarList.getItems()) { if ((entry.getSummary().equals(calendar_name)) || (calendar_name.equals("primary")) && entry.isPrimary()) { calendarID = entry; logger.debug("Got calendar {} CalendarID: {}", calendar_name, calendarID.getId()); } } } if (calendarID == null) { logger.warn("Calendar {} not found", calendar_name); } return calendarID; } }