Working prototype OAuth 2 authentication for Google Contacts plugin.

maven
Danny van Heumen 11 years ago
parent 7d311ba178
commit dcbeba9b6a

@ -84,5 +84,9 @@
<classpathentry kind="lib" path="lib/installer-exclude/hsqldb.jar"/>
<classpathentry kind="lib" path="lib/installer-exclude/irc-api-1.0.jar" sourcepath="lib/installer-exclude/irc-api-1.0-sources.jar"/>
<classpathentry kind="lib" path="lib/installer-exclude/commons-codec-1.4.jar"/>
<classpathentry kind="lib" path="lib/installer-exclude/google-http-client-1.20.0.jar"/>
<classpathentry kind="lib" path="lib/installer-exclude/google-http-client-jackson2-1.20.0.jar"/>
<classpathentry kind="lib" path="lib/installer-exclude/google-oauth-client-1.20.0.jar"/>
<classpathentry kind="lib" path="lib/installer-exclude/jackson-core-2.5.3.jar"/>
<classpathentry kind="output" path="classes"/>
</classpath>

@ -1391,6 +1391,10 @@
<zipfileset src="${lib.noinst}/gdata-contacts-meta-3.0.jar" prefix=""/>
<zipfileset src="${lib.noinst}/gdata-client-1.0.jar" prefix=""/>
<zipfileset src="${lib.noinst}/gdata-client-meta-1.0.jar" prefix=""/>
<zipfileset src="${lib.noinst}/google-oauth-client-1.20.0.jar" prefix=""/>
<zipfileset src="${lib.noinst}/google-http-client-1.20.0.jar" prefix=""/>
<zipfileset src="${lib.noinst}/google-http-client-jackson2-1.20.0.jar" prefix=""/>
<zipfileset src="${lib.noinst}/jackson-core-2.5.3.jar" prefix=""/>
</jar>
</target>

@ -6,17 +6,25 @@
*/
package net.java.sip.communicator.impl.googlecontacts;
import java.io.*;
import java.util.concurrent.atomic.*;
import net.java.sip.communicator.service.googlecontacts.*;
import net.java.sip.communicator.util.*;
import com.google.gdata.client.*;
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.*;
/**
* Google Contacts credentials to connect to the service.
*
* @author Sebastien Vincent
* @author Danny van Heumen
*/
public class GoogleContactsConnectionImpl
implements GoogleContactsConnection
@ -27,6 +35,39 @@ public class GoogleContactsConnectionImpl
private static final Logger logger =
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);
/**
* Login.
*/
@ -121,26 +162,127 @@ public void setPassword(String password)
*
* @return connection status
*/
public ConnectionStatus connect()
public synchronized ConnectionStatus connect()
{
createCredential(GOOGLE_OAUTH2_TOKEN_SERVER);
googleService.setOAuth2Credentials(this.credential.get());
return ConnectionStatus.SUCCESS;
}
/**
* Query for contacts using provided ContactQuery.
*
* Executes query. In case of failure, refresh OAuth2 token and retry query.
* If query fails again, throws FailedContactQueryException.
*
* @param query the contact query
* @return Returns the contact feed with matching contacts.
* @throws IOException
* @throws ServiceException
* @throws FailedContactQueryException Throws in case of failed query.
* @throws FailedTokenRefreshException Throws in case refreshing OAuth2
* token fails.
*/
public synchronized ContactFeed query(final ContactQuery query)
throws IOException,
ServiceException,
FailedContactQueryException,
FailedTokenRefreshException
{
try
{
googleService.setUserCredentials(login, password);
return this.googleService.query(query, ContactFeed.class);
}
catch(AuthenticationException e)
catch (Exception e)
{
logger.info("Google contacts connection failure: " + e);
if(e instanceof GoogleService.InvalidCredentialsException)
// FIXME if possible narrow down the exceptions on which to
// refresh token
logger.info("Failed to execute query. Going to refresh token"
+ " and try again.", e);
refreshToken();
}
try
{
return this.googleService.query(query, ContactFeed.class);
}
catch (Exception e)
{
throw new FailedContactQueryException(e);
}
}
/**
* 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
{
return ConnectionStatus.ERROR_INVALID_CREDENTIALS;
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());
}
else
});
builder.addRefreshListener(new CredentialRefreshListener()
{
final AtomicReference<Credential> store =
GoogleContactsConnectionImpl.this.credential;
@Override
public void onTokenResponse(Credential credential,
TokenResponse tokenResponse) throws IOException
{
return ConnectionStatus.ERROR_UNKNOWN;
logger.debug("Successful token refresh response: "
+ tokenResponse.toPrettyString());
store.set(credential);
}
}
return ConnectionStatus.SUCCESS;
@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);
}
/**
@ -182,4 +324,24 @@ public String getPrefix()
{
return prefix;
}
public static class FailedContactQueryException
extends Exception
{
private FailedContactQueryException(Throwable cause)
{
super("Failed to query Google Contacts API.", cause);
}
}
public static class FailedTokenRefreshException
extends Exception
{
private FailedTokenRefreshException()
{
super("Failed to refresh OAuth2 token.");
}
}
}

@ -269,14 +269,11 @@ public List<GoogleContactsEntry> searchContact(
try
{
contactFeed = cnxImpl.getGoogleService().query(
query, ContactFeed.class);
contactFeed = cnxImpl.query(query);
}
catch(Exception e)
{
logger.info(
"Problem occurred during Google Contacts retrievment",
e);
logger.warn("Problem occurred during Google Contacts query", e);
return ret;
}

@ -24,4 +24,5 @@ Import-Package: org.osgi.framework,
javax.swing.event,
javax.swing.table,
javax.swing.tree,
javax.swing.text
javax.swing.text,
javax.net.ssl

Loading…
Cancel
Save