0

We are looking to push periodic or event driven Glass Timeline updates and we're running into issues with the AuthUtil provided in the Java Glass starter project.

Normally, we would just need to call the following after a user has enabled our app with Google permissions:

Credential credential = AuthUtil.getCredential(googleUserToken);

This works perfectly when retrieving the Google UserToken from an HttpRequest during the authentication servlet or the notification servlet.

We are persisting the user's Google User Token at the App authentication phase. However, the same valid Google UserTokens returns a null Credential object when attempting to get the Google Credential from a Quartz Job or Message Queue.

We have debugged the code and noticed that the AuthUtil is using the ListableMemoryCredentialStore and this store is empty when trying to retrieve a Credential from a Quartz job.

Dayel Ostraco
  • 1,673
  • 1
  • 13
  • 15

1 Answers1

2

Going to answer my own question here. The AuthUtil helper packaged with the starter project provides only an in-memory cache of Google Credentials. In the event of a server restart or new instance, the cache is gone and the user is then directed to the Google OAuth flow.

To prevent this and provide scalability to your Glass app, use the following CredentialStore implementation and replace the use of the ListableMemoryCredentialStore in AuthUtil. It's not the cleanest, but it's lightweight and preserves the singleton intent of the AuthUtil.

Replace the JDBC paths to your database, schema, username and password for each of the databse helper methods. The Google credential table is nothing more than a four column table storing the userId as the PK, accessToken, resfreshToken and expriationTimeInMillis. Obviously, feel free to adjust the data model as you see fit.

/**
 * Created by Dayel Ostraco
 * Date: 12/4/13
 *
 * This extends the Google OAuth2 CredentialStore interface and uses a Database backend to     persist Google Credentials
 * in a thread safe manner.
 *
 */
public class DatabaseCredentialStore implements CredentialStore {

private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseCredentialStore.class);

/**
 * Lock on access to the database Credential store.
 */
private final Lock lock = new ReentrantLock();

/**
 * Stores a Google OAuth2 Credential in the database.
 *
 * @param userId String
 * @param credential Google OAuth2 Credential
 */
public void store(String userId, Credential credential) {
    lock.lock();
    try {
        DatabasePersistedCredential item = findByUserId(userId);
        if (item == null) {
            item = new DatabasePersistedCredential();
            item.store(userId, credential);
            create(item);
        }
        item.store(userId, credential);
    } finally {
        lock.unlock();
    }
}

/**
 * Removes a persisted Google Credential from the database.
 *
 * @param userId String
 * @param credential Google OAuth2 Credential
 */
public void delete(String userId, Credential credential) {
    lock.lock();
    try {
        DatabasePersistedCredential item = findByUserId(credential.getAccessToken());
        delete(item);
    } finally {
        lock.unlock();
    }
}

/**
 * Loads a Google Credential with the contents of the persisted Google Credentials.
 *
 * @param userId String
 * @param credential Google OAuth2 Credential
 * @return boolean
 */
public boolean load(String userId, Credential credential) {
    lock.lock();
    try {
        DatabasePersistedCredential item = findByUserId(userId);
        if (item != null) {
            item.load(userId, credential);
        }
        return item != null;
    } finally {
        lock.unlock();
    }
}

/**
 * Returns all users in the Google Credentials Table
 * @return List of all persisted Google authenticated user IDs
 */
public List<String> listAllUsers() {

    List<String> userIds = new ArrayList<>();

    for(DatabasePersistedCredential credential : findAll()) {
        userIds.add(credential.getUserId());
    }

    return userIds;
}

/****************
 * JDBC Methods *
 ***************/

/**
 * Persists a new Google Credential to the database.
 *
 * @param credential DatabasePersistedCredential
 */
private void create(DatabasePersistedCredential credential) {

    Connection connect;
    PreparedStatement preparedStatement;

    try {
        Class.forName("com.mysql.jdbc.Driver");
        connect = DriverManager.getConnection("jdbc:mysql://yourdatabaseurl:3306/schema?user=un&password=password");

        preparedStatement = connect.prepareStatement("INSERT INTO GoogleCredential values (?, ?, ?, ?)");
        preparedStatement.setString(1, credential.getUserId());
        preparedStatement.setString(2, credential.getAccessToken());
        preparedStatement.setString(3, credential.getRefreshToken());
        preparedStatement.setLong(4, credential.getExpirationTimeInMillis());
        preparedStatement.executeUpdate();

    } catch (ClassNotFoundException e) {
        LOGGER.error("Could not load MySQL Driver.", e);
    } catch (SQLException e) {
        LOGGER.error("Could not persist DatabasePersistedCredential.", e);
    }
}

/**
 * Removes a Google credential from the database.
 *
 * @param credential DatabasePersistedCredential
 */
private void delete(DatabasePersistedCredential credential) {

    Connection connect;
    PreparedStatement preparedStatement;
    ResultSet resultSet;

    try {
        Class.forName("com.mysql.jdbc.Driver");
        connect = DriverManager.getConnection("jdbc:mysql://yourdatabaseurl:3306/schema?user=un&password=password");

        preparedStatement = connect.prepareStatement("DELETE * FROM spgglassdb.GoogleCredential WHERE userId = ?;");
        preparedStatement.setString(1, credential.getUserId());
        preparedStatement.executeQuery();

    } catch (ClassNotFoundException e) {
        LOGGER.error("Could not load MySQL Driver.", e);
    } catch (SQLException e) {
        LOGGER.error("Could not persist DatabasePersistedCredential.", e);
    }
}

/**
 * Returns the Google credentials for a user with the passed in userId.
 *
 * @param userId String
 * @return DatabasePersistedCredential
 */
private DatabasePersistedCredential findByUserId(String userId) {

    Connection connect;
    PreparedStatement preparedStatement;
    ResultSet resultSet;

    try {
        Class.forName("com.mysql.jdbc.Driver");
        connect = DriverManager.getConnection("jjdbc:mysql://yourdatabaseurl:3306/schema?user=un&password=password");

        preparedStatement = connect.prepareStatement("SELECT * FROM spgglassdb.GoogleCredential WHERE userId = ?;");
        preparedStatement.setString(1, userId);
        resultSet = preparedStatement.executeQuery();

        List<DatabasePersistedCredential> credentials = convertResultsSet(resultSet);
        if(credentials.size()==1) {
            return credentials.get(0);
        } else {
            return null;
        }

    } catch (ClassNotFoundException e) {
        LOGGER.error("Could not load MySQL Driver.", e);
    } catch (SQLException e) {
        LOGGER.error("Could not persist DatabasePersistedCredential.", e);
    }

    return null;
}

/**
 * Returns all DatabasePersistedCredentials located in the database.
 *
 * @return List<DatabasePersistedCredential>
 */
private List<DatabasePersistedCredential> findAll() {

    Connection connect;
    PreparedStatement preparedStatement;
    ResultSet resultSet;

    try {
        Class.forName("com.mysql.jdbc.Driver");
        connect = DriverManager.getConnection("jdbc:mysql://yourdatabaseurl:3306/schema?user=un&password=password");

        preparedStatement = connect.prepareStatement("SELECT * FROM spgglassdb.GoogleCredential;");
        resultSet = preparedStatement.executeQuery();

        List<DatabasePersistedCredential> credentials = convertResultsSet(resultSet);
        return credentials;

    } catch (ClassNotFoundException e) {
        LOGGER.error("Could not load MySQL Driver.", e);
    } catch (SQLException e) {
        LOGGER.error("Could not persist DatabasePersistedCredential.", e);
    }

    return null;
}

/**
 * Converts a ResultSet to a collection of DatabasePersistedCredentials.
 *
 * @param resultSet JDBC ResultSet
 * @return List<DatabasePersistedCredential>
 * @throws SQLException
 */
private List<DatabasePersistedCredential> convertResultsSet(ResultSet resultSet) throws SQLException {

    List<DatabasePersistedCredential> credentials = new ArrayList<>();

    while (resultSet.next()) {
        DatabasePersistedCredential credential = new DatabasePersistedCredential();
        String accessToken = resultSet.getString("accessToken");
        String refreshToken = resultSet.getString("refreshToken");
        Long expirationTimeInMillis = resultSet.getLong("expirationTimeInMillis");

        credential.setAccessToken(accessToken);
        credential.setRefreshToken(refreshToken);
        credential.setExpirationTimeInMillis(expirationTimeInMillis);

        credentials.add(credential);
    }

    return credentials;
}
}
Dayel Ostraco
  • 1,673
  • 1
  • 13
  • 15