aboutsummaryrefslogtreecommitdiffstats
path: root/src/net/java/sip/communicator/impl
diff options
context:
space:
mode:
authorDanny van Heumen <danny@dannyvanheumen.nl>2015-06-10 00:19:09 +0200
committerDanny van Heumen <danny@dannyvanheumen.nl>2015-07-20 22:29:45 +0200
commit218b69991e594ea56e29f809803900473a47b487 (patch)
treea74d5893850d97cedfcaf49727adfddf11a72556 /src/net/java/sip/communicator/impl
parentdcbeba9b6a262ce0ab99b96e673a7ef1ec8b5acd (diff)
downloadjitsi-218b69991e594ea56e29f809803900473a47b487.zip
jitsi-218b69991e594ea56e29f809803900473a47b487.tar.gz
jitsi-218b69991e594ea56e29f809803900473a47b487.tar.bz2
Working on simple UI to facilitate user approval process.
Diffstat (limited to 'src/net/java/sip/communicator/impl')
-rw-r--r--src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsConnectionImpl.java136
-rw-r--r--src/net/java/sip/communicator/impl/googlecontacts/OAuth2TokenStore.java340
2 files changed, 354 insertions, 122 deletions
diff --git a/src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsConnectionImpl.java b/src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsConnectionImpl.java
index 46849aa..7c0bafc 100644
--- a/src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsConnectionImpl.java
+++ b/src/net/java/sip/communicator/impl/googlecontacts/GoogleContactsConnectionImpl.java
@@ -7,15 +7,12 @@
package net.java.sip.communicator.impl.googlecontacts;
import java.io.*;
-import java.util.concurrent.atomic.*;
+import net.java.sip.communicator.impl.googlecontacts.OAuth2TokenStore.FailedAcquireCredentialException;
+import net.java.sip.communicator.impl.googlecontacts.OAuth2TokenStore.FailedTokenRefreshException;
import net.java.sip.communicator.service.googlecontacts.*;
import net.java.sip.communicator.util.*;
-import com.google.api.client.auth.oauth2.*;
-import com.google.api.client.http.*;
-import com.google.api.client.http.javanet.*;
-import com.google.api.client.json.jackson2.*;
import com.google.gdata.client.contacts.*;
import com.google.gdata.data.contacts.*;
import com.google.gdata.util.*;
@@ -36,37 +33,9 @@ public class GoogleContactsConnectionImpl
Logger.getLogger(GoogleContactsConnectionImpl.class);
/**
- * Google OAuth 2 token server.
- */
- private static final GenericUrl GOOGLE_OAUTH2_TOKEN_SERVER =
- new GenericUrl("https://accounts.google.com/o/oauth2/token");
-
- /**
- * Client ID for OAuth 2 based authentication.
- */
- private static final String GOOGLE_API_CLIENT_ID = null;
-
- /**
- * Client secret for OAuth 2 based authentication.
- */
- private static final String GOOGLE_API_CLIENT_SECRET = null;
-
- // FIXME Actually use scopes!
- private static final String[] GOOGLE_API_OAUTH2_SCOPES = new String[]
- { "profile", "email", "https://www.google.com/m8/feeds" };
-
- // FIXME Actually use the redirect URL!
- private static final String GOOGLE_API_OAUTH2_REDIRECT_URI =
- "urn:ietf:wg:oauth:2.0:oob";
-
- // FIXME (Danny) Temporary stored refresh token during development...
- private static final String TEMP_REFRESH_TOKEN =
- "1/mjFeVE86qVmm-O2B6SSLweW6hBcj4qxuYSb0fGvJvH0";
-
- /**
* The credential store to pass around.
*/
- private final AtomicReference<Credential> credential = new AtomicReference<Credential>(null);
+ private final OAuth2TokenStore store = new OAuth2TokenStore();
/**
* Login.
@@ -164,9 +133,16 @@ public class GoogleContactsConnectionImpl
*/
public synchronized ConnectionStatus connect()
{
- createCredential(GOOGLE_OAUTH2_TOKEN_SERVER);
- googleService.setOAuth2Credentials(this.credential.get());
- return ConnectionStatus.SUCCESS;
+ try
+ {
+ googleService.setOAuth2Credentials(this.store.get());
+ return ConnectionStatus.SUCCESS;
+ }
+ catch (FailedAcquireCredentialException e)
+ {
+ logger.error("Failed to acquire credentials.", e);
+ return ConnectionStatus.ERROR_UNKNOWN;
+ }
}
/**
@@ -199,7 +175,7 @@ public class GoogleContactsConnectionImpl
// refresh token
logger.info("Failed to execute query. Going to refresh token"
+ " and try again.", e);
- refreshToken();
+ this.store.refresh();
}
try
{
@@ -212,80 +188,6 @@ public class GoogleContactsConnectionImpl
}
/**
- * Refresh OAuth2 authentication token.
- *
- * @throws IOException
- */
- private void refreshToken() throws IOException, FailedTokenRefreshException
- {
- final Credential credential = this.credential.get();
- if (!credential.refreshToken())
- {
- logger.warn("Refresh of OAuth2 authentication token failed.");
- throw new FailedTokenRefreshException();
- }
- }
-
- /**
- * Create credential instance suitable for use in Google Contacts API.
- * @param tokenServer the token server URL
- * @return Returns a Credential instance.
- */
- private void createCredential(final GenericUrl tokenServerURL)
- {
- final Credential.Builder builder =
- new Credential.Builder(
- BearerToken.authorizationHeaderAccessMethod());
- builder.setTokenServerUrl(tokenServerURL);
- builder.setTransport(new NetHttpTransport());
- builder.setJsonFactory(new JacksonFactory());
- builder.setClientAuthentication(new HttpExecuteInterceptor()
- {
-
- @Override
- public void intercept(HttpRequest request) throws IOException
- {
- final RefreshTokenRequest content =
- (RefreshTokenRequest) ((UrlEncodedContent) request
- .getContent()).getData();
- content.put("client_id", GOOGLE_API_CLIENT_ID);
- content.put("client_secret", GOOGLE_API_CLIENT_SECRET);
- logger.warn("Refresh token request: " + content.toString());
- }
- });
- builder.addRefreshListener(new CredentialRefreshListener()
- {
- final AtomicReference<Credential> store =
- GoogleContactsConnectionImpl.this.credential;
-
- @Override
- public void onTokenResponse(Credential credential,
- TokenResponse tokenResponse) throws IOException
- {
- logger.debug("Successful token refresh response: "
- + tokenResponse.toPrettyString());
- store.set(credential);
- }
-
- @Override
- public void onTokenErrorResponse(Credential credential,
- TokenErrorResponse tokenErrorResponse) throws IOException
- {
- logger.debug("Failed token refresh response: "
- + tokenErrorResponse.toPrettyString());
- logger.error("Failed to refresh OAuth2 token: "
- + tokenErrorResponse.getError() + ": "
- + tokenErrorResponse.getErrorDescription());
- }
- });
- final Credential credential = builder.build();
- credential.setAccessToken("ya29.iwG37LYEgB4FCwfPLq8vV6Q-CX1vQ5sJrb_2AGydhLAiUT4wmz4iW4FlVkZE57s1B6NgA3BJAspLIw");
- credential.setRefreshToken(TEMP_REFRESH_TOKEN);
- credential.setExpiresInSeconds(3600L);
- this.credential.set(credential);
- }
-
- /**
* Returns if the connection is enabled.
*
* @return true if connection is enabled, false otherwise
@@ -334,14 +236,4 @@ public class GoogleContactsConnectionImpl
super("Failed to query Google Contacts API.", cause);
}
}
-
- public static class FailedTokenRefreshException
- extends Exception
- {
-
- private FailedTokenRefreshException()
- {
- super("Failed to refresh OAuth2 token.");
- }
- }
}
diff --git a/src/net/java/sip/communicator/impl/googlecontacts/OAuth2TokenStore.java b/src/net/java/sip/communicator/impl/googlecontacts/OAuth2TokenStore.java
new file mode 100644
index 0000000..18a909d
--- /dev/null
+++ b/src/net/java/sip/communicator/impl/googlecontacts/OAuth2TokenStore.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright @ 2015 Atlassian Pty Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package net.java.sip.communicator.impl.googlecontacts;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.io.*;
+import java.net.*;
+import java.util.concurrent.atomic.*;
+
+import javax.swing.*;
+
+import net.java.sip.communicator.plugin.desktoputil.*;
+import net.java.sip.communicator.util.*;
+
+import com.google.api.client.auth.oauth2.*;
+import com.google.api.client.http.*;
+import com.google.api.client.http.javanet.*;
+import com.google.api.client.json.jackson2.*;
+
+/**
+ * OAuth 2 token store.
+ *
+ * @author Danny van Heumen
+ */
+public class OAuth2TokenStore
+{
+
+ /**
+ * Logger.
+ */
+ private static final Logger LOGGER = Logger.getLogger(OAuth2TokenStore.class);
+
+ /**
+ * Google OAuth 2 token server.
+ */
+ private static final GenericUrl GOOGLE_OAUTH2_TOKEN_SERVER =
+ new GenericUrl("https://accounts.google.com/o/oauth2/token");
+
+ /**
+ * Client ID for OAuth 2 based authentication.
+ */
+ private static final String GOOGLE_API_CLIENT_ID = null;
+
+ /**
+ * Client secret for OAuth 2 based authentication.
+ */
+ private static final String GOOGLE_API_CLIENT_SECRET = null;
+
+ /**
+ * Required OAuth 2 authentication scopes.
+ */
+ private static final String GOOGLE_API_OAUTH2_SCOPES =
+ "profile%20email%20https://www.google.com/m8/feeds";
+
+ /**
+ * OAuth 2 redirect URL.
+ */
+ private static final String GOOGLE_API_OAUTH2_REDIRECT_URI =
+ "urn:ietf:wg:oauth:2.0:oob";
+
+ /**
+ * 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);
+
+ /**
+ * The credential store.
+ *
+ * Note: The AtomicReference container is used as a shared container that is
+ * also passed on to some of the registered listeners for updating the
+ * credential data.
+ */
+ private final AtomicReference<Credential> store =
+ new AtomicReference<Credential>(null);
+
+ /**
+ * Get the credential from the store. In case a credential does not (yet)
+ * exist, acquire one preferrably from the password store. Optionally,
+ * involve the user if a credential is not yet stored.
+ *
+ * @return Returns the credential.
+ * @throws FailedAcquireCredentialException
+ * @throws MalformedURLException In case requesting authn token failed.
+ */
+ public Credential get() throws FailedAcquireCredentialException
+ {
+ if (this.store.get() == null)
+ {
+ try
+ {
+ acquireCredential(this.store);
+ }
+ catch (Exception e)
+ {
+ throw new FailedAcquireCredentialException(e);
+ }
+ }
+ // should make sure that only succeeded requests reach up to here
+ return this.store.get();
+ }
+
+ /**
+ * Acquire a new credential instance.
+ *
+ * @param store credential store to update upon refreshing and other
+ * operations
+ * @return Acquires and returns the credential instance.
+ * @throws MalformedURLException In case of bad redirect URL.
+ * @throws URISyntaxException In case of bad redirect URI.
+ */
+ private static void acquireCredential(
+ final AtomicReference<Credential> store)
+ throws MalformedURLException,
+ URISyntaxException
+ {
+ 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));
+ }
+
+ /**
+ * Refresh OAuth2 authentication token.
+ *
+ * @throws IOException
+ * @throws FailedTokenRefreshException In case of failed token refresh
+ * operation.
+ */
+ public void refresh() throws IOException, FailedTokenRefreshException
+ {
+ final Credential credential = this.store.get();
+ if (credential == null)
+ {
+ throw new IllegalStateException("A credential instance should "
+ + "exist, but it does not. This is likely due to a bug.");
+ }
+ if (!credential.refreshToken())
+ {
+ LOGGER.warn("Refresh of OAuth2 authentication token failed.");
+ throw new FailedTokenRefreshException();
+ }
+ }
+
+ /**
+ * Create credential instance suitable for use in Google Contacts API.
+ *
+ * @param store reference to the credential store for updating credential
+ * data upon refreshing and other cases
+ * @param approvalCode the approval code received from Google by the user
+ * accepting the authorization request
+ * @return Returns a Credential instance.
+ * @throws URISyntaxException In case of bad OAuth 2 redirect URI.
+ */
+ private static Credential createCredential(
+ final AtomicReference<Credential> store, final TokenData data) throws URISyntaxException
+ {
+ final Credential.Builder builder =
+ new Credential.Builder(
+ BearerToken.authorizationHeaderAccessMethod());
+ builder.setTokenServerUrl(GOOGLE_OAUTH2_TOKEN_SERVER);
+ builder.setTransport(new NetHttpTransport());
+ builder.setJsonFactory(new JacksonFactory());
+ builder.setClientAuthentication(new HttpExecuteInterceptor()
+ {
+
+ @Override
+ public void intercept(HttpRequest request) throws IOException
+ {
+ final Object data =
+ ((UrlEncodedContent) request.getContent()).getData();
+ if (data instanceof RefreshTokenRequest)
+ {
+ // Insert client authentication credentials in requests.
+ final RefreshTokenRequest content =
+ (RefreshTokenRequest) data;
+ 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.");
+ if (LOGGER.isDebugEnabled())
+ {
+ LOGGER.debug("Request: " + content.toString());
+ }
+ }
+ else
+ {
+ LOGGER.info("Unexpected type of request found.");
+ }
+ }
+ });
+ builder.addRefreshListener(new CredentialRefreshListener()
+ {
+
+ @Override
+ public void onTokenResponse(Credential credential,
+ TokenResponse tokenResponse) throws IOException
+ {
+ LOGGER.debug("Successful token refresh response: "
+ + tokenResponse.toPrettyString());
+ store.set(credential);
+ }
+
+ @Override
+ public void onTokenErrorResponse(Credential credential,
+ TokenErrorResponse tokenErrorResponse) throws IOException
+ {
+ if (LOGGER.isDebugEnabled())
+ {
+ LOGGER.debug("Failed token refresh response: "
+ + tokenErrorResponse.toPrettyString());
+ }
+ LOGGER.error("Failed to refresh OAuth2 token: "
+ + tokenErrorResponse.getError() + ": "
+ + tokenErrorResponse.getErrorDescription());
+ }
+ });
+ final Credential credential = builder.build();
+ credential.setAccessToken(data.accessToken);
+ credential.setRefreshToken(data.refreshToken);
+ credential.setExpiresInSeconds(data.expiration);
+ return credential;
+ }
+
+ private static TokenData requestAuthenticationToken(final String approvalCode)
+ {
+ // FIXME actually acquire credential
+ return new TokenData("", "", 3600L);
+ }
+
+ private static class OAuthApprovalDialog extends SIPCommDialog {
+ private static final long serialVersionUID = 6792589736608633346L;
+
+ private final SIPCommLinkButton label;
+
+ private final SIPCommTextField code = new SIPCommTextField("");
+
+ public OAuthApprovalDialog() throws MalformedURLException
+ {
+ this.setModal(true);
+ this.label = new SIPCommLinkButton("Click here to approve.");
+ this.label.addActionListener(new ActionListener()
+ {
+
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ LOGGER.info("Request user for approval via web page: " + APPROVAL_URL);
+ // FIXME open browser
+ }
+ });
+ this.setLayout(new BorderLayout());
+ this.add(this.label, BorderLayout.NORTH);
+ this.add(new JLabel("Code"), BorderLayout.WEST);
+ this.add(this.code, BorderLayout.CENTER);
+ final JButton button = new JButton("Done");
+ button.addActionListener(new ActionListener() {
+
+ @Override
+ public void actionPerformed(ActionEvent e)
+ {
+ OAuthApprovalDialog.this.dispose();
+ }
+ });
+ this.add(button, BorderLayout.SOUTH);
+ this.pack();
+ }
+
+ public String getApprovalCode() {
+ return this.code.getText();
+ }
+ }
+
+ private static class TokenData
+ {
+ private final String accessToken;
+
+ private final String refreshToken;
+
+ private final long expiration;
+
+ private TokenData(final String accessToken, final String refreshToken, final long expirationTime)
+ {
+ if (accessToken == null)
+ {
+ throw new NullPointerException("access token cannot be null");
+ }
+ this.accessToken = accessToken;
+ if (refreshToken == null)
+ {
+ throw new NullPointerException("refresh token cannot be null");
+ }
+ this.refreshToken = refreshToken;
+ this.expiration = expirationTime;
+ }
+ }
+
+ public static class FailedAcquireCredentialException
+ extends Exception
+ {
+ private static final long serialVersionUID = 5810534617383420431L;
+
+ private FailedAcquireCredentialException(final Throwable cause)
+ {
+ super(cause);
+ }
+ }
+
+ public static class FailedTokenRefreshException
+ extends Exception
+ {
+ private static final long serialVersionUID = 3166027054735734199L;
+
+ private FailedTokenRefreshException()
+ {
+ super("Failed to refresh OAuth2 token.");
+ }
+ }
+}