diff options
author | Danny van Heumen <danny@dannyvanheumen.nl> | 2015-07-15 00:23:07 +0200 |
---|---|---|
committer | Danny van Heumen <danny@dannyvanheumen.nl> | 2015-07-20 22:29:45 +0200 |
commit | 37a8c45280eced1ea33392416fbe0b39c71fe0a1 (patch) | |
tree | 3261be0abd59ac5d35d93f18dd77c39029a1b422 /src/net/java/sip/communicator/impl | |
parent | d00c3f896c741617f8367f4896d890d1aa1ba2fd (diff) | |
download | jitsi-37a8c45280eced1ea33392416fbe0b39c71fe0a1.zip jitsi-37a8c45280eced1ea33392416fbe0b39c71fe0a1.tar.gz jitsi-37a8c45280eced1ea33392416fbe0b39c71fe0a1.tar.bz2 |
Added automatic refresh token retrieval + secure storage in credentials store.
Google Contacts plugin now automatically requests initial access token
and refresh token at token server. The refresh token is stored using the
credentials store. And Activator is cleaned up a bit.
To fix:
1. size of OAuth 2 approval dialog such that requested identity is
clearly visible.
2. Automatically open web browser on Google OAuth 2 approval page.
Diffstat (limited to 'src/net/java/sip/communicator/impl')
5 files changed, 194 insertions, 56 deletions
diff --git a/src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsActivator.java b/src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsActivator.java index 7c33c7d..75c6f14 100644 --- a/src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsActivator.java +++ b/src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsActivator.java @@ -13,6 +13,7 @@ import net.java.sip.communicator.service.credentialsstorage.*; import net.java.sip.communicator.service.googlecontacts.*; import net.java.sip.communicator.service.gui.*; import net.java.sip.communicator.service.protocol.*; +import net.java.sip.communicator.service.resources.*; import net.java.sip.communicator.util.*; import org.jitsi.service.configuration.*; @@ -37,7 +38,7 @@ public class GoogleContactsActivator implements BundleActivator * The OSGi <tt>ServiceRegistration</tt> of * <tt>GoogleContactsServiceImpl</tt>. */ - private ServiceRegistration serviceRegistration = null; + private ServiceRegistration<?> serviceRegistration = null; /** * BundleContext from the OSGI bus. @@ -67,8 +68,8 @@ public class GoogleContactsActivator implements BundleActivator /** * List of contact source service registrations. */ - private static Map<GoogleContactsSourceService, ServiceRegistration> cssList - = new HashMap<GoogleContactsSourceService, ServiceRegistration>(); + private static Map<GoogleContactsSourceService, ServiceRegistration<?>> cssList + = new HashMap<GoogleContactsSourceService, ServiceRegistration<?>>(); /** * The registered PhoneNumberI18nService. @@ -86,12 +87,9 @@ public class GoogleContactsActivator implements BundleActivator { if(configService == null) { - ServiceReference confReference - = bundleContext.getServiceReference( - ConfigurationService.class.getName()); - configService - = (ConfigurationService) bundleContext.getService( - confReference); + configService = + ServiceUtils.getService(bundleContext, + ConfigurationService.class); } return configService; } @@ -120,12 +118,9 @@ public class GoogleContactsActivator implements BundleActivator { if(credentialsService == null) { - ServiceReference confReference - = bundleContext.getServiceReference( - CredentialsStorageService.class.getName()); - credentialsService - = (CredentialsStorageService) bundleContext.getService( - confReference); + credentialsService = + ServiceUtils.getService(bundleContext, + CredentialsStorageService.class); } return credentialsService; } @@ -142,12 +137,8 @@ public class GoogleContactsActivator implements BundleActivator { if(resourceService == null) { - ServiceReference confReference - = bundleContext.getServiceReference( - ResourceManagementService.class.getName()); - resourceService - = (ResourceManagementService) bundleContext.getService( - confReference); + resourceService = + ResourceManagementServiceUtils.getService(bundleContext); } return resourceService; } @@ -207,7 +198,7 @@ public class GoogleContactsActivator implements BundleActivator */ private void serviceChanged(ServiceEvent event) { - ServiceReference serviceRef = event.getServiceReference(); + ServiceReference<?> serviceRef = event.getServiceReference(); // if the event is caused by a bundle being stopped, we don't want to // know @@ -303,7 +294,7 @@ public class GoogleContactsActivator implements BundleActivator } /* remove contact source services */ - for(Map.Entry<GoogleContactsSourceService, ServiceRegistration> entry : + for(Map.Entry<GoogleContactsSourceService, ServiceRegistration<?>> entry : cssList.entrySet()) { if (entry.getValue() != null) @@ -338,7 +329,7 @@ public class GoogleContactsActivator implements BundleActivator { GoogleContactsSourceService css = new GoogleContactsSourceService( login, password); - ServiceRegistration cssServiceRegistration = null; + ServiceRegistration<?> cssServiceRegistration = null; css.setGoogleTalk(googleTalk); @@ -380,7 +371,7 @@ public class GoogleContactsActivator implements BundleActivator boolean googleTalk) { GoogleContactsSourceService css = new GoogleContactsSourceService(cnx); - ServiceRegistration cssServiceRegistration = null; + ServiceRegistration<?> cssServiceRegistration = null; css.setGoogleTalk(googleTalk); @@ -418,7 +409,7 @@ public class GoogleContactsActivator implements BundleActivator { GoogleContactsSourceService found = null; - for(Map.Entry<GoogleContactsSourceService, ServiceRegistration> entry : + for(Map.Entry<GoogleContactsSourceService, ServiceRegistration<?>> entry : cssList.entrySet()) { String cssName = entry.getKey().getLogin(); @@ -459,7 +450,7 @@ public class GoogleContactsActivator implements BundleActivator return; } - for(Map.Entry<GoogleContactsSourceService, ServiceRegistration> entry : + for(Map.Entry<GoogleContactsSourceService, ServiceRegistration<?>> entry : cssList.entrySet()) { String cssName = entry.getKey().getLogin(); diff --git a/src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsConnectionImpl.java b/src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsConnectionImpl.java index 503b413..52d03da 100644 --- a/src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsConnectionImpl.java +++ b/src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsConnectionImpl.java @@ -135,7 +135,7 @@ public class GoogleContactsConnectionImpl { try { - googleService.setOAuth2Credentials(this.store.get()); + googleService.setOAuth2Credentials(this.store.get(this.login)); return ConnectionStatus.SUCCESS; } catch (FailedAcquireCredentialException e) diff --git a/src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsServiceImpl.java b/src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsServiceImpl.java index 206f4aa..adcd198 100644 --- a/src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsServiceImpl.java +++ b/src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsServiceImpl.java @@ -68,7 +68,7 @@ public class GoogleContactsServiceImpl /** * Path where to store the account settings */ - private final static String CONFIGURATION_PATH = + final static String CONFIGURATION_PATH = "net.java.sip.communicator.impl.googlecontacts"; /** diff --git a/src/net/java/sip/communicator/impl/googlecontacts/OAuth2TokenStore.java b/src/net/java/sip/communicator/impl/googlecontacts/OAuth2TokenStore.java index cd04b39..3dfaaea 100644 --- a/src/net/java/sip/communicator/impl/googlecontacts/OAuth2TokenStore.java +++ b/src/net/java/sip/communicator/impl/googlecontacts/OAuth2TokenStore.java @@ -19,16 +19,27 @@ import java.awt.*; import java.awt.event.*; import java.io.*; import java.net.*; +import java.util.*; import java.util.concurrent.atomic.*; import javax.swing.*; import net.java.sip.communicator.plugin.desktoputil.*; +import net.java.sip.communicator.service.credentialsstorage.*; import net.java.sip.communicator.util.*; +import org.apache.http.HttpResponse; +import org.apache.http.client.*; +import org.apache.http.client.entity.*; +import org.apache.http.client.methods.*; +import org.apache.http.impl.client.*; +import org.apache.http.message.*; + import com.google.api.client.auth.oauth2.*; import com.google.api.client.http.*; +import com.google.api.client.http.HttpRequest; import com.google.api.client.http.javanet.*; +import com.google.api.client.json.*; import com.google.api.client.json.jackson2.*; /** @@ -42,7 +53,37 @@ public class OAuth2TokenStore /** * Logger. */ - private static final Logger LOGGER = Logger.getLogger(OAuth2TokenStore.class); + private static final Logger LOGGER = Logger + .getLogger(OAuth2TokenStore.class); + + /** + * Symbol for refresh token in token server response. + */ + private static final String REFRESH_TOKEN_SYMBOL = "refresh_token"; + + /** + * Symbol for access token in token server response. + */ + private static final String ACCESS_TOKEN_SYMBOL = "access_token"; + + /** + * Symbol for expiration time in token server response. + */ + private static final String EXPIRES_IN_SYMBOL = "expires_in"; + + /** + * Interesting token server response fields. + */ + private static final Set<String> TOKEN_RESPONSE_FIELDS; + + static + { + final HashSet<String> set = new HashSet<String>(); + set.add(REFRESH_TOKEN_SYMBOL); + set.add(ACCESS_TOKEN_SYMBOL); + set.add(EXPIRES_IN_SYMBOL); + TOKEN_RESPONSE_FIELDS = Collections.unmodifiableSet(set); + } /** * Google OAuth 2 token server. @@ -73,13 +114,17 @@ public class OAuth2TokenStore "urn:ietf:wg:oauth:2.0:oob"; /** + * Grant type for communication with token server. + */ + private static final String GOOGLE_API_GRANT_TYPE = "authorization_code"; + + /** * Approval URL. */ private static final String APPROVAL_URL = String.format( - "https://accounts.google.com/o/oauth2/auth?scope=%s&redirect_uri=%s&response_type=code&client_id=%s", - GOOGLE_API_OAUTH2_SCOPES, - GOOGLE_API_OAUTH2_REDIRECT_URI, - GOOGLE_API_CLIENT_ID); + "https://accounts.google.com/o/oauth2/auth?scope=%s&redirect_uri=%s" + + "&response_type=code&client_id=%s", GOOGLE_API_OAUTH2_SCOPES, + GOOGLE_API_OAUTH2_REDIRECT_URI, GOOGLE_API_CLIENT_ID); /** * The credential store. @@ -96,17 +141,19 @@ public class OAuth2TokenStore * exist, acquire one preferrably from the password store. Optionally, * involve the user if a credential is not yet stored. * + * @param identity The identity of the API token. * @return Returns the credential. * @throws FailedAcquireCredentialException * @throws MalformedURLException In case requesting authn token failed. */ - public Credential get() throws FailedAcquireCredentialException + public Credential get(final String identity) + throws FailedAcquireCredentialException { if (this.store.get() == null) { try { - acquireCredential(this.store); + acquireCredential(this.store, identity); } catch (Exception e) { @@ -122,23 +169,67 @@ public class OAuth2TokenStore * * @param store credential store to update upon refreshing and other * operations + * @param identity the identity to which the refresh token belongs * @return Acquires and returns the credential instance. - * @throws MalformedURLException In case of bad redirect URL. * @throws URISyntaxException In case of bad redirect URI. + * @throws IOException + * @throws ClientProtocolException */ private static void acquireCredential( - final AtomicReference<Credential> store) - throws MalformedURLException, - URISyntaxException + final AtomicReference<Credential> store, final String identity) + throws URISyntaxException, ClientProtocolException, IOException { - LOGGER.info("No credentials available yet. Requesting user to " - + "approve access to Contacts API using URL: " + APPROVAL_URL); - final OAuthApprovalDialog dialog = new OAuthApprovalDialog(); - dialog.setVisible(true); - final String approvalCode = dialog.getApprovalCode(); - LOGGER.debug("Approval code from user: " + approvalCode); - final TokenData data = requestAuthenticationToken(approvalCode); - store.set(createCredential(store, data)); + final TokenData token; + String refreshToken = restoreRefreshToken(identity); + if (refreshToken == null) + { + LOGGER.info("No credentials available yet. Requesting user to " + + "approve access to Contacts API for identity " + identity + + " using URL: " + APPROVAL_URL); + final OAuthApprovalDialog dialog = + new OAuthApprovalDialog(identity); + dialog.setVisible(true); + final String approvalCode = dialog.getApprovalCode(); + LOGGER.debug("Approval code from user: " + approvalCode); + token = requestAuthenticationToken(approvalCode); + saveRefreshToken(token, identity); + } + else + { + token = new TokenData("TOKEN_NOT_AVAILABLE", refreshToken, 0); + } + store.set(createCredential(store, token)); + } + + /** + * Restore refresh token from encrypted credentials store. + * + * @param identity The identity corresponding to the refresh token. + * @return Returns the refresh token. + */ + private static String restoreRefreshToken(final String identity) + { + final CredentialsStorageService credentials = + GoogleContactsActivator.getCredentialsService(); + return credentials + .loadPassword(GoogleContactsServiceImpl.CONFIGURATION_PATH + "." + + identity); + } + + /** + * Save refresh token for provided identity. + * + * @param token The refresh token. + * @param identity The identity. + * @throws IOException An IOException in case of errors. + */ + private static void saveRefreshToken(final TokenData token, + final String identity) throws IOException + { + final CredentialsStorageService credentials = + GoogleContactsActivator.getCredentialsService(); + credentials.storePassword(GoogleContactsServiceImpl.CONFIGURATION_PATH + + "." + identity, token.refreshToken); } /** @@ -174,7 +265,8 @@ public class OAuth2TokenStore * @throws URISyntaxException In case of bad OAuth 2 redirect URI. */ private static Credential createCredential( - final AtomicReference<Credential> store, final TokenData data) throws URISyntaxException + final AtomicReference<Credential> store, final TokenData data) + throws URISyntaxException { final Credential.Builder builder = new Credential.Builder( @@ -198,7 +290,7 @@ public class OAuth2TokenStore content.put("client_id", GOOGLE_API_CLIENT_ID); content.put("client_secret", GOOGLE_API_CLIENT_SECRET); LOGGER.info("Inserting client authentication data into " - + " refresh token request."); + + "refresh token request."); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Request: " + content.toString()); @@ -249,11 +341,57 @@ public class OAuth2TokenStore * * @param approvalCode the approval code * @return Returns the acquired token data from OAuth 2 token server. + * @throws IOException + * @throws ClientProtocolException */ - private static TokenData requestAuthenticationToken(final String approvalCode) + private static TokenData requestAuthenticationToken( + final String approvalCode) throws ClientProtocolException, IOException { - // FIXME actually acquire credential - return new TokenData("", "", 3600L); + final HttpClient client = new DefaultHttpClient(); + final HttpPost post = new HttpPost(GOOGLE_OAUTH2_TOKEN_SERVER.toURI()); + final UrlEncodedFormEntity entity = + new UrlEncodedFormEntity(Arrays.asList(new BasicNameValuePair( + "code", approvalCode), new BasicNameValuePair("client_id", + GOOGLE_API_CLIENT_ID), new BasicNameValuePair("client_secret", + GOOGLE_API_CLIENT_SECRET), new BasicNameValuePair( + "redirect_uri", GOOGLE_API_OAUTH2_REDIRECT_URI), + new BasicNameValuePair("grant_type", GOOGLE_API_GRANT_TYPE))); + post.setEntity(entity); + final HttpResponse httpResponse = client.execute(post); + final JsonParser parser = + JacksonFactory.getDefaultInstance().createJsonParser( + httpResponse.getEntity().getContent()); + try + { + // Token response components initialized with defaults in case + // fields are missing in the token server response. + String accessToken = ""; + String refreshToken = ""; + long expiresIn = 3600; + // Parse token server response. + String found; + while (parser.nextToken() != JsonToken.END_OBJECT) + { + found = parser.skipToKey(TOKEN_RESPONSE_FIELDS); + if (REFRESH_TOKEN_SYMBOL.equals(found)) + { + refreshToken = parser.getText(); + } + else if (ACCESS_TOKEN_SYMBOL.equals(found)) + { + accessToken = parser.getText(); + } + else if (EXPIRES_IN_SYMBOL.equals(found)) + { + expiresIn = parser.getLongValue(); + } + } + return new TokenData(accessToken, refreshToken, expiresIn); + } + finally + { + parser.close(); + } } /** @@ -270,10 +408,13 @@ public class OAuth2TokenStore private final SIPCommTextField code = new SIPCommTextField(""); - public OAuthApprovalDialog() + public OAuthApprovalDialog(final String identity) { this.setModal(true); - this.label = new SIPCommLinkButton("Click here to approve."); + this.label = + new SIPCommLinkButton( + "Click here to approve. Make sure you log in as " + + identity); this.label.addActionListener(new ActionListener() { diff --git a/src/net/java/sip/communicator/impl/googlecontacts/googlecontacts.manifest.mf b/src/net/java/sip/communicator/impl/googlecontacts/googlecontacts.manifest.mf index 99337a8..4fd9387 100644 --- a/src/net/java/sip/communicator/impl/googlecontacts/googlecontacts.manifest.mf +++ b/src/net/java/sip/communicator/impl/googlecontacts/googlecontacts.manifest.mf @@ -25,4 +25,10 @@ Import-Package: org.osgi.framework, javax.swing.table, javax.swing.tree, javax.swing.text, - javax.net.ssl + javax.net.ssl, + org.apache.http, + org.apache.http.client, + org.apache.http.client.entity, + org.apache.http.client.methods, + org.apache.http.impl.client, + org.apache.http.message |