I'm creating an Android app that uses a regular Google Drive account as an application-owned account as described, with incredible brevity, here:
Use regular Google accounts as application-owned accounts
Unfortunately all the descriptions of how to do anything on Drive refer to a client secret, and as described here:
Google APIs Console - missing client secret
that doesn't really exist any more for installed apps. I managed to get an OAuth2 token, at incredible pains, by building an actual web page. From this I obtained an access token and a refresh token, as described in the first link. I want to make a GoogleCredential object with these, and as far as I can see this should work like this:
HttpTransport httpTransport = new NetHttpTransport();
JsonFactory jsonFactory = new JacksonFactory();
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setClientSecrets(
new GoogleClientSecrets().setInstalled(
new Details()
.setAuthUri(AUTH_URI)
.setClientId(CLIENT_ID)
//.setClientSecret("but I haven't got one!")
.setRedirectUris(REDIRECT_URIS)
)
)
.build()
.setAccessToken(ACCESS_TOKEN) //defined above
.setRefreshToken(REFRESH_TOKEN); //defined above
Drive.Builder driveBuilder = new Drive.Builder(httpTransport, jsonFactory, null);
driveBuilder.setHttpRequestInitializer(credential);
I've put in the setClientSecret(...) call as commented out, because I don't have a client secret for the installed application.
Basically, I can't make this credential, and every suggestion about making credentials I have seen falls into one of these three categories:
designed for users to log into their own account. Not acceptable; I want one Drive account for all users BUT a regular account, not a service account (the client specified).
based on AccountManager or some other system of authentication. Not acceptable for the same reason.
throws up its hands in horror at the egregiousness of the Google API. Suggests making the request the old-fashioned way.
By the way, without the client secret set, I get a NullPointerException in Preconditions.class. It doesn't tell me a lot about this. I also tried to make a GoogleCredential directly by subclassing it, and it instructed me to use a GoogleCredential.Builder, which is why I find myself here.
What do I do to get round this client secret problem?
The answer seems to be to uncomment the line. The GoogleCredential.Builder succeeds in constructing a credential object as long as something is in the client secret field. But does it actually get the drive? Stick around.
EDIT: no, I'm getting an "invalid_client" error. This is intensely frustrating.
Here's a link to a question that provides its own answer in an edit, using the help of the accepted answer:
How setup Google Drive Credentials within Android App?
In short, the main thing is that you need to get a simple api access key from the developer api console. If you can't find it, then drive api access probably needs to be set.
Related
Background
Suppose I want to search for some email addresses on Github using code (Kotlin/Java).
The problem
I've succeeded doing it without any login token, but as I've read it's limited to just 10 queries per minute, and if I have a token from the user (from login to Github), it adds 30 queries per minute.
To get information from Github without login, I use OKHTTP to reach this (found from here):
https://api.github.com/search/users?q=$email+in:EMAIL_ADDRESS_HERE
And if I could use a token, it would probably be:
https://api.github.com/search/users?q=$email+in:EMAIL_ADDRESS_HERE&access_token=$TOKEN_HERE
But I don't get how to get this token from the user. I can find how to make one for myself, using the website itself.
What I've found and tried
I asked Github about how it's done, but they showed me some curl code, which sadly I'm not familiar with and I have no idea how it's done there and how to convert it to Kotlin/Java. I tried to read for alternatives, but then I've found some missing information about how it's done (missing prior data that is required for the parameters). I don't even get if it's working by using a WebView or directly contacting the Github servers. If it's directly contacting the Github servers, doesn't it mean that I need to have userName&password EditTexts for the user?
This is the code I've seen on the tutorial:
JsonFactory jsonFactory = new JacksonFactory();
HttpTransport httpTransport = new NetHttpTransport();
AuthorizationCodeFlow flow = new AuthorizationCodeFlow.Builder(
BearerToken.authorizationHeaderAccessMethod(),
httpTransport, jsonFactory,
new GenericUrl("https://github.com/login/oauth/access_token"),
new ClientParametersAuthentication( /* Client ID and Secret */ ),
/* Client ID */
"https://github.com/login/oauth/authorize").build();
TokenResponse tokenResponse = flow
.newTokenRequest(code)
.setScopes(Collections.singletonList("user:email"))
.setRequestInitializer(new HttpRequestInitializer() {
#Override
public void initialize(HttpRequest request) throws IOException {
request.getHeaders().setAccept("application/json");
}
}).execute();
So, what's missing here is "Client ID and Secret" , "code", and the part that the user himself provides : the user-name and password to get the token.
The questions
How do I get into the whole login process, and finally get a token to be used?
Should it use a WebView to offer the user with all options to login, or should I use my own UI to put userName&password?
Github supports OAuth 2.0 for authentication. You can setup your application on Github to enable this feature as per this example and you can also use AppAuth to handle the authentication journey.
I followed the official document for implementing sing-in with Google. It was not difficult, but the official document stopped at getting the basic information such as name or e-mail. I need to get birthday.
So, when creating the option, I added this:
.requestScopes(Scope("https://www.googleapis.com/auth/user.birthday.read"))
Since the official document stopped there I could not get how to get birthday. So I have searched the web and found a method using a plain HTTP GET request to https://people.googleapis.com/v1/people/me. (Or is this covered by the Google Play Services API?)
But the method needs the access token, which was not included in the GoogleSignInAccount which was returned after signing in. There was an example for getting the access ID by creating an OAuth server ID and then use getServerAuthCode() to get the access token, but that is complicated and it is not done on a server, it is done inside the app, right after singing in.
In this case, do I have to create an OAuth server ID, and use the access token to get the birthday?
After a lot of Google searching, I have found this example. Before finding this, I found an example of using Plus.API, but this answer said it was deprecated. The funny thing is, now even this answer is outdated and does not work. I had to search a lot more to fix it. If only Google's sign-in document page included an example of getting additional data other than the default fields, all these miseries could have been avoided.
Anyways, this is the code that worked for me.
First, sign in with Google according to the official documentation.
Then,
val HTTP_TRANSPORT = AndroidHttp.newCompatibleTransport();
val JSON_FACTORY = JacksonFactory.getDefaultInstance();
inner class DamnTask(var email:String) :AsyncTask<Unit,Unit,Unit>()
{
override fun doInBackground(vararg params: Unit?)
{
var credential = GoogleAccountCredential.usingOAuth2(context,
Collections.singletonList(Scopes.PROFILE));
credential.selectedAccount = Account(email, "com.google");
var service = People.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential)
.setApplicationName("MyApp")
.build();
var me = service.people().get("people/me")
.setRequestMaskIncludeField("person.Birthdays")
.execute();
Log.d("damn", "Birthday " +me.birthdays.toString());
}
}
To make it work, I had to add the following libraries to the dependencies.
compile 'com.google.api-client:google-api-client:1.23.0'
compile 'com.google.api-client:google-api-client-android:1.22.0'
compile 'com.google.apis:google-api-services-people:v1-rev4-1.22.0'
Also, I had to enable the "People" API in the project's settings. This needs to be done in the Google's web console. I forgot the URL, but if you do not enable it, the exception message will show the exact URL to enable it.
You can find more field names here.
Google has just released a new way to let users sign in to an application with their google account:
http://android-developers.blogspot.de/2016/05/improving-security-and-user-experience.html
https://developers.google.com/identity/sign-in/android/start-integrating
Since it's recommended to use the new API, I'd like to do so. However, I'm not quite sure yet about some of the steps. It's clear to me how to implement the sign in workflow itself (the sample application is quite easy to understand). However, how do I get the API service handle for my backend service?
Until now, I used to do it like this: First, get a GoogleAccountCredential like this:
GoogleAccountCredential credential = GoogleAccountCredential.usingAudience(
context, "server:client_id:" + WEB_CLIENT_ID);
credential.setSelectedAccountName("example#gmail.com");
Then, I passed these credentials on to the automatically generated android client library for the app engine endpoints:
User.Builder b = new User.Builder(AndroidHttp.newCompatibleTransport(),
new AndroidJsonFactory(), CREDENTIALS_FROM_STEP_1).setApplicationName("App");
That was it. However, I have no idea how to get the GoogleAccountCredential when using the new sign in workflow. Has anyone got an idea? Do I need to update the backend as well? As I said, the whole OAuth2 communication and authorization with my backend is done by the auto-generated client libraries.
I've set up a project in Google API's for use with BigQuery. I've generated Client ID credentials and am using the BigQuery client for Android.
GoogleAccountCredential credential = GoogleAccountCredential.usingOAuth2(getApplicationContext(), Collections.singleton(BigqueryScopes.BIGQUERY));
...Display User Picker...
credential.setSelectedAccountName("account.selected.by.user#gmail.com");
Bigquery bigQuery = new Bigquery.Builder(AndroidHttp.newCompatibleTransport(), GsonFactory.getDefaultInstance(), credential)
.setApplicationName("My-App/1.0").build()
...use bigQuery to run a job or do whatever...
We can successfully connect to BigQuery, but only for Project Member Accounts as seen at https://console.developers.google.com/project/apps~my-project-name/permissions. Use of other accounts results in 403 Access Denied JSON errors from the BigQuery Client.
The app is to be deployed to any number of users where we do not know their accounts ahead of time. This workflow doesn't support that, unless I'm missing some trick.
It's starting to smell like we need to set up an App Engine app, use GoogleAccountCredential.usingAudience, and set up web services as a pass-through to BigQuery.
Any ideas or thoughts will be appreciated with up votes.
This can be accomplished by using a Service Account, which is typically geared for server-to-server communication.
Follow the instructions at https://developers.google.com/bigquery/docs/authorization#service-accounts-server.
GoogleCredential credential = new GoogleCredential.Builder().setTransport(TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId("XXXXXXX#developer.gserviceaccount.com")
.setServiceAccountScopes(SCOPE)
.setServiceAccountPrivateKeyFromP12File(new File("my_file.p12"))
.build();
bigquery = new Bigquery.Builder(TRANSPORT, JSON_FACTORY, credential)
.setApplicationName("BigQuery-Service-Accounts/0.1")
.setHttpRequestInitializer(credential).build();
Save the p12 file to your assets folder, and generate a physical File object out of the InputStream from the p12 file in assets.
In this way, you can have your Android application act as a non-user-centric client of BigQuery. Awesome!
In the Google documentation for OAuth2, building a GoogleCredential with an auth token is described here:
Credential and Credential Store
In particular this code snippet is offered:
GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken);
Plus plus = Plus.builder(new NetHttpTransport(), new JacksonFactory())
.setApplicationName("Google-PlusSample/1.0")
.setHttpRequestInitializer(credential)
.build()
When I try to build a GoogleCredential in this way I am tersely informed:
Please use the Builder and call setJsonFactory, setTransport and setClientSecrets
in the message field of an exception. I downloaded the libraries for this last week so I am not sure what is happening. Is the documentation simply outdated, and if so, what method has replaced this one as best practice for building from an existing auth token and refresh token?
Incidentally, the reason using the Builder was not an option was that there WAS no client secret provided by the Google application console; it says that they are no longer provided for Android apps and the like. setClientSecrets(...), therefore, couldn't be called.
I met this problem recently and figured out the solution for my case.
Here is the running conditions: the program is running on Android 4.0 and does not use Google Drive SDK, because it could not allow our program to read the files on Google Drive which are not created by our program. I use the com.google.api.services.drive.* and com.google.api.client.googleapis.auth.oauth2.* Java libraries.
the code arose this problem is like below :
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
httpTransport, jsonFactory, CLIENT_ID, CLIENT_SECRET, Arrays.asList(DriveScopes.DRIVE))
.setAccessType("offline")
.setApprovalPrompt("auto").build();
GoogleTokenResponse response = null;
try {
response = flow.newTokenRequest(authorizationCode).setRedirectUri(REDIRECT_URI).execute();
} catch (IOException e) {
e.printStackTrace();
}
GoogleCredential credential = new GoogleCredential().setFromTokenResponse(response);
but above code worked well if the parameter to setAccessType("online");
and the resolution for this issue depends on what kind of accessType you want.
if you want "onLine" as the accessType, then use setFromTokenResponse()should be ok.
if you want "offline" as the accessType, then you need to use blow code :
GoogleCredential credential = new GoogleCredential.Builder().setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setClientSecrets(CLIENT_ID, CLIENT_SECRET)
.build()
.setFromTokenResponse(response);
One thing need to be mentioned is that you need to keep the accessType setting to be consistent for GoogleTokenResponse and GoogleAuthorizationCodeFlow creation.
You need to set up your Android app key in Google Dev Console.
Choose your project, select API & Auth, then click Credentials
Create new client id (though it has other client ids)
Select installed app -> android
Fill in your package name and SHA1 correctly
Create new Key (though it has other client keys)
Select Android key
Fill in the SHA1;packageName like this: 45:B5:E4:6F:36:AD:0A:98:94:B4:02:66:2B:12:17:F2:56:26:A0:E0;com.example
Your problem will be automatically solved. Be sure to create client id and key with both your debug keystore and release keystore.
Check the Android quickstart sample in the Google Drive SDK documentation for step-by-step instructions to correctly setup GoogleCredential:
https://developers.google.com/drive/quickstart-android