Android App: Acquire Access Token for Google Drive API - google-drive-api

I am writing an Android (version ICS) app. which uploads data to the Google Drive. The app
uses oauth2 to acquire the access token.
First step: acquire authorization token.
String AUTH_TOKEN_TYPE = "oauth2:https://www.googleapis.com/auth/drive";
// Step 1
accountManager.getAuthToken(
account, // Account retrieved using getAccountsByType("com.google")
AUTH_TOKEN_TYPE, // Auth Token Type
options, // Authenticator-specific options
this, // Your activity
new OnTokenAcquired(), // Callback called when a token is successfully acquired
new Handler(new OnAuthTokenError())); // Callback called if an error occurs
}
private class OnTokenAcquired implements AccountManagerCallback<Bundle> {
#Override
public void run(AccountManagerFuture<Bundle> result) {
// Get the result of the operation from the AccountManagerFuture.
Bundle bundle;
try {
bundle = result.getResult();
authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
Log.d(TAG,"authToken:" + authToken);
exchangeToken access = (exchangeToken) new exchangeToken().execute();
} catch (OperationCanceledException e) {
e.printStackTrace();
} catch (AuthenticatorException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Success. An authorization token is acquired.
Step 2: Exchange authorization token for Access Token.
private class exchangeToken extends AsyncTask<String, Void, Void> {
#Override
protected Void doInBackground(String... params) {
HttpTransport transport = new NetHttpTransport();
JsonFactory jsonFactory = new GsonFactory();
String CLIENT_ID = "999999999999.apps.googleusercontent.com";
String CLIENT_SECRET = "axXXXXXXXXXXXXXXX7";
try { // Step 2: Exchange for an access and refresh token
GoogleTokenResponse authResponse = new GoogleAuthorizationCodeTokenRequest(transport, jsonFactory, CLIENT_ID, CLIENT_SECRET, authToken, CALLBACK_URL).execute();
accessToken = authResponse.getAccessToken();
Log.d("Get Access","Token:" + accessToken);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
Fail. The LogCat shows the following:
com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad Request
{
"error":"unauthorized_client"
}
I have been able to access "Google Drive" on my Android tablet using the "Drive" app. so
my email account is valid. May be the AUTH_TOKEN_TYPE is incorrect, but the Google Drive
SDK is not clear what it must be. What am I missing?

You do not need to do the second step of exchanging the token. Android grants you an access token directly, it does not grant you an auth code which you would have to exchange for tokens.
This page on the Android documentation explains everything really well.

You know that for using the Drive API your users have to install your app on the Chrome(!) Webstore?
Normally Documents List API is the better choice from Android.

Related

Can we use the same signer object to sign all the requests?

I need to make mutiple rest api calls for fetching instance, volume and vnic details. Can i reuse the same signer object created for signing the other calls?
Signer object method
public RequestSigner getSigner(Properties properties, String pemFilePath, String apiKey) {
InputStream privateKeyStream;
PrivateKey privateKey = null;
try {
privateKeyStream = Files.newInputStream(Paths.get(pemFilePath));
privateKey = PEM.readPrivateKey(privateKeyStream);
} catch (InvalidKeySpecException e) {
// throw new RuntimeException("Invalid format for private key");
properties.setProperty(OracleCloudConstants.CUSTOM_DC_ERROR,
FormatUtil.getString("am.webclient.oraclecloud.customdc.invalidformat"));
AMLog.debug("OracleCloudDataCollector::CheckAuthentication()::Invalid format for private key::"
+ e.getMessage());
e.printStackTrace();
} catch (IOException e) {
properties.setProperty(OracleCloudConstants.CUSTOM_DC_ERROR,
FormatUtil.getString("am.webclient.oraclecloud.customdc.failedload"));
AMLog.debug(
"OracleCloudDataCollector::CheckAuthentication()::Failed to load private key::" + e.getMessage()); //No I18N
e.printStackTrace();
// throw new RuntimeException("Failed to load private key");
}
RequestSigner signer = null;
if (privateKey != null) {
signer = new RequestSigner(apiKey, privateKey);
}
return signer;
}
One signer object may be used to sign multiple requests. In fact, the SDK implementation does this too.
It is not clear what version of the SDK you are using. In version 1.5.7 (the most recent at the time of writing), com.oracle.bmc.http.signing.RequestSigner (https://github.com/oracle/oci-java-sdk/blob/master/bmc-common/src/main/java/com/oracle/bmc/http/signing/RequestSigner.java#L16) is an interface which cannot be new’ed as per the snippet above.

Cordova InAppBrowser accessing certificate on virtual smartcard

I have an app running on Windows Phone 8.1 which calls a URL via InAppBrowser plugin. This URL is supposed to ask for the user certificate stored on a virtual smartcard on the phone.
When I call the URL via Internet Explorer, I am asked for my PIN to unlock the virtual smartcard but in the InAppBrowser, this doesn't work. No PIN prompt, nothing.
Iterating through the Certificates yielded from
IReadOnlyList<Certificate> certStores = await CertificateStores.FindAllAsync();
I can see the certificate at app runtime but InAppBrowser doesn't seem to query for them. Do I have to copy its reference to another certificate store or is InAppBrowser not capable of establishing SSL with user certificates ?
The issue is with the webview component, x-ms-webview to be more precisely. InAppBrowser plugin uses this component internally.
Found a workaround mentioned here, it kinda sounds like a security issue tbh so this could get fixed in the future but here are more details on said workaround:
Make a request to the URL which is supposed to trigger virtual smartcard unlock to access the user certificate, but with the HttpClient at native level (C#)
I've created another Windows Runtime Component in my solution which does a simple POST to the url I want to access from InAppBrowser later on.
While setting up the Windows.Web.Http.HttpClient, I fetch the user certificate from the smartcard and set it as HttpBaseProtocolFilter.ClientCertificate.
public sealed class SSLHelper
{
private static String errorMessage = "";
private static String statusMessage = "";
public static IAsyncOperation<Boolean> establishSSLConnection(String url)
{
return connect(url).AsAsyncOperation<Boolean>();
}
public static String getErrorMessage()
{
return SSLHelper.errorMessage;
}
public static String getStatusMessage()
{
return SSLHelper.statusMessage;
}
private static async Task<Boolean> connect(String urlString)
{
Certificate clientCert = await getCertificateAsync();
HttpBaseProtocolFilter filter = new HttpBaseProtocolFilter();
filter.ClientCertificate = clientCert;
HttpClient client = new HttpClient(filter);
try
{
System.Uri url = new System.Uri(urlString);
HttpResponseMessage response = await client.PostAsync(url, new HttpStringContent(""));
response.EnsureSuccessStatusCode();
SSLHelper.statusMessage = response.StatusCode.ToString();
return true;
}
catch (Exception e)
{
SSLHelper.errorMessage = e.ToString();
return false;
}
}
private static async Task<Certificate> getCertificateAsync()
{
CertificateQuery query = new CertificateQuery();
query.IssuerName = "Sample Issuer";
IReadOnlyList<Certificate> certStores = await CertificateStores.FindAllAsync(query);
return certStores.FirstOrDefault<Certificate>();
}
}
Make that code return as a promise on Javascript level and once it resolves, start the code which uses InAppBrowser to access the secure URL again. The native request causes the PIN prompt for virtual smartcard access, once you have entered the correct PIN, InAppBrowser / WebView can magically establish the connection.

Refresh token and reuse this token to get new access token java

How do I get the refresh token and the access token from the first time authorization code? And, how do I reuse this refresh token to get a new access token to upload to Google Drive using the Java API? This is not a web application. It's in Java Swing code.
We can reuse the refresh token to get new access token by following code
public class OAuthRefreshToken implements CredentialRefreshListener {
public static GoogleCredential getAccessTokenFromRefreshToken( String refreshToken, HttpTransport transport, com.google.api.client.json.JsonFactory jsonFactory, String CLIENT_ID, String CLIENT_SECRET) throws IOException
{
GoogleCredential.Builder credentialBuilder = new GoogleCredential.Builder()
.setTransport(transport).setJsonFactory(jsonFactory)
.setClientSecrets(CLIENT_ID, CLIENT_SECRET);
credentialBuilder.addRefreshListener(new OAuthRefreshToken());
GoogleCredential credential = credentialBuilder.build();
credential.setRefreshToken(refreshToken);
credential.refreshToken();
return credential;
}
#Override
public void onTokenErrorResponse(Credential arg0, TokenErrorResponse arg1)
throws IOException {
// TODO Auto-generated method stub
System.out.println("Error occured !");
System.out.println(arg1.getError());
}
#Override
public void onTokenResponse(Credential arg0, TokenResponse arg1)
throws IOException {
// TODO Auto-generated method stub
System.out.println(arg0.getAccessToken());
System.out.println(arg0.getRefreshToken());
}
}
Here is a solution I recently made up from the basic example in the Google Drive docs and some experimenting:
The IApiKey contains the static strings CLIENT_ID, and so on. ITokenPersistence is an interface which allows to load and save a token (as String). It decouples the persistence mechanism (I used Preferences for an Eclipse e4 RCP application) from the Uploader. This can be as simple as storing the token in a file The IAthorizationManager is an interface which is is used to let the user grant acces and enter the code to create the refresh token. I implemented a Dialog containing a browser widget to grant access and a text box to copy and paste the code to. The custom exception GoogleDriveException hides the API classes from the rest of the code.
public final class Uploader implements IApiKey {
public static final String TEXT_PLAIN = "text/plain";
private final ITokenPersistence tokenManager;
private final IAuthorizationManager auth;
public Uploader(final ITokenPersistence tm, final IAuthorizationManager am) {
this.tokenManager = tm;
this.auth = am;
}
private GoogleCredential createCredentialWithRefreshToken(
final HttpTransport transport,
final JsonFactory jsonFactory,
final String clientId,
final String clientSecret,
final TokenResponse tokenResponse) {
return new GoogleCredential.Builder().setTransport(transport)
.setJsonFactory(jsonFactory)
.setClientSecrets(clientId, clientSecret)
.build()
.setFromTokenResponse(tokenResponse);
}
/**
* Upload the given file to Google Drive.
* <P>
* The name in Google Drive will be the same as the file name.
* #param fileContent a file of type text/plain
* #param description a description for the file in Google Drive
* #return Answer the ID of the uploaded file in Google Drive.
* Answer <code>null</code> if the upload failed.
* #throws IOException
* #throws {#link GoogleDriveException} when a <code>TokenResponseException</code> had been
* intercepted while inserting (uploading) the file.
*/
public String upload(final java.io.File fileContent, final String description) throws IOException, GoogleDriveException {
HttpTransport httpTransport = new NetHttpTransport();
JsonFactory jsonFactory = new JacksonFactory();
// If we do not already have a refresh token a flow is created to get a refresh token.
// To get the token the user has to visit a web site and enter the code afterwards
// The refresh token is saved and may be reused.
final GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
httpTransport,
jsonFactory,
CLIENT_ID,
CLIENT_SECRET,
Arrays.asList(DriveScopes.DRIVE))
.setAccessType("offline")
.setApprovalPrompt("auto").build();
final String url = flow.newAuthorizationUrl().setRedirectUri(REDIRECT_URI).build();
final String refreshToken = this.tokenManager.loadRefreshToken();
GoogleCredential credential = null;
if( refreshToken == null ) {
// no token available: get one
String code = this.auth.authorize(url);
GoogleTokenResponse response = flow.newTokenRequest(code).setRedirectUri(REDIRECT_URI).execute();
this.tokenManager.saveRefreshToken(response.getRefreshToken());
credential = this.createCredentialWithRefreshToken(httpTransport, jsonFactory, CLIENT_ID, CLIENT_SECRET, response);
}
else {
// we have a token, if it is expired or revoked by the user the service call (see below) may fail
credential = new GoogleCredential.Builder()
.setJsonFactory(jsonFactory)
.setTransport(httpTransport)
.setClientSecrets(CLIENT_ID, CLIENT_SECRET)
.build();
credential.setRefreshToken(refreshToken);
}
//Create a new authorized API client
final Drive service = new Drive.Builder(httpTransport, jsonFactory, credential)
.setApplicationName(APP_NAME)
.build();
//Insert a file
final File body = new File();
body.setTitle(fileContent.getName());
body.setDescription(description);
body.setMimeType(TEXT_PLAIN);
final FileContent mediaContent = new FileContent(TEXT_PLAIN, fileContent);
try {
final File file = service.files().insert(body, mediaContent).execute();
return ( file != null ) ? file.getId() : null;
} catch (TokenResponseException e) {
e.printStackTrace();
throw new GoogleDriveException(e.getDetails().getErrorDescription(), e.getCause());
}
}
}

service.files().list().setQ("sharedWithMe") doesn't return files shared by my Android App

I have successfully uploaded a file to Drive and inserted permissions of another account to share. All this is working. But when I try from the other account to list out the shared files for download, it returns that there are no files.
Does anyone know how to retreive files that are shared from another user?
Here is what I've tried.
private class RetrieveAllShareFiles extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... arg0) {
Files.List request = null;
try {
request = service.files().list().setQ("sharedWithMe");
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
do {
try {
FileList files = request.execute();
filesResult.addAll(files.getItems());
request.setPageToken(files.getNextPageToken());
} catch (IOException e) {
System.out.println("An error occurred: " + e);
request.setPageToken(null);
}
} while (request.getPageToken() != null &&
request.getPageToken().length() > 0);
return "Executed";
}
When using the drive.files scope your app will only be able to access files that you have created with it or that the user is opening from the Drive UI.
When the file is shared with another user, he won't be able to see it using the drive.files scope. For this use case you should request access to the full Drive scope.
Check the documentation for more details on the available OAuth scopes: https://developers.google.com/drive/scopes

Google Drive API Upload returns File ID/Title but document does not exist in Drive

I have followed the examples given on the Google Drive SDK site for Authorization via Service Accounts (https://developers.google.com/drive/service-accounts) and to insert a file (https://developers.google.com/drive/v2/reference/files/insert). I have managed to get it working using the Client ID/Client secret with oauth2 but need automation so want to use the private key.
My issue is I am given a file id, Title, Description & MIME type in return e.g. File ID: %s0B6ysbMIcH3AGWHJPRmZUTVZZMnM, Title: My document, Description: A test document, MIME type: text/plain but the document does -not- exist in Drive and no errors are returned.
I have been work on this for 2 days without success and would really appreciate any assistance. I have looked on-line and the examples I have found are similar to the below. I have tried multiple Google accounts (one a company Google Apps & another a normal gmail account with the same result).
The code (with the account info changed) :
public class AutoGoogleDrive {
private static final String SERVICE_ACCOUNT_PKCS12_FILE_PATH = "/home/jsmith/Java/11111111111111111111111111-privatekey.p12";
private static final String SERVICE_ACCOUNT_EMAIL = "1111111111111#developer.gserviceaccount.com";
public static Drive getDriveService() throws GeneralSecurityException,
IOException, URISyntaxException {
HttpTransport httpTransport = new NetHttpTransport();
JacksonFactory jsonFactory = new JacksonFactory();
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
.setServiceAccountScopes(DriveScopes.DRIVE_FILE)
.setServiceAccountPrivateKeyFromP12File(
new java.io.File(SERVICE_ACCOUNT_PKCS12_FILE_PATH))
.build();
Drive service = new Drive.Builder(httpTransport, jsonFactory, null)
.setHttpRequestInitializer(credential).build();
return service;
}
public static void main(String[] args) throws IOException {
Drive service = null;
try {
service = getDriveService();
} catch (GeneralSecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (URISyntaxException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//Insert a text file
File body = new File();
body.setTitle("My document");
body.setDescription("A test document");
body.setMimeType("text/plain");
// File's content.
java.io.File fileContent = new java.io.File("/home/jsmith/document.txt");
FileContent mediaContent = new FileContent("text/plain", fileContent);
try {
File file = service.files().insert(body, mediaContent).execute();
// Uncomment the following line to print the File ID.
System.out.println("File ID: %s" + file.getId());
System.out.println("Title: " + file.getTitle());
System.out.println("Description: " + file.getDescription());
System.out.println("MIME type: " + file.getMimeType());
} catch (IOException e) {
System.out.println("An error occured: " + e);
}
}
}
Thanks,
Joe Smith
When using service accounts, the inserted file will be added to the application's Drive account for which there's no Drive UI. Those files are only available through the API.