Authorize scopes after renewing credentials and app access to google account - java

Here is my problem.
I'm developping a web app using google app engine in JAVA.
I've searched the web and saw a lot of different answers to my problem, giving various solutions but none of them worked for me I still get:
{
"code" : 403,
"errors" : [ {
"domain" : "global",
"message" : "Insufficient Permission",
"reason" : "insufficientPermissions"
} ],
"message" : "Insufficient Permission"
}
I'm trying to send a mail from my account to other email adresses (from me to me as a test).
I've implemented successfully the sheets API but remembered I had to go first through a consent Screen but now I really don't know how to popup this screen again to validate scopes permanently, Let me explain myself.
I have two credentials files.
for retrieving users infos to confirm sign in through Google, here I use an openID connect URL and there I user get the consent screen.
use Sheets API and Gmail API as "me" to first retrieve datas from my spreadsheets and send mail from my email adress. As told before I've validated the Sheets API but can't get Gmail to work with the same credentials.
I've tested various scopes add another credential file without any success. Please tell me if you need some code.
EDIT
Just deleted my Sheets API credentials also removed my app fto be linked to my account and now I cannot get the consent screen to popup. and I get:
com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad Request
{
"error" : "invalid_grant",
"error_description" : "Token has been expired or revoked."
}
I'm using this code to get credentials
private static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT) throws IOException {
// Load client secrets.
InputStreamReader in = new InputStreamReader(new FileInputStream(new File("WEB-INF/credentials.json")));
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, in);
// Build flow and trigger user authorization request.
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
.setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH)))
.setAccessType("offline")
.build();
LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build();
return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
}
but google documentation says:
Authorization information is stored on the file system, so subsequent
executions will not prompt for authorization.
Where can I delete the stored file system?
Any help is greatly appreciated

After messing around hours and hours I've finally came with an unexplainable solution.
I remove my app from this page
then change my code to
return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
to this
return new AuthorizationCodeInstalledApp(flow, receiver).authorize(null);
to make appear consent screen.
then I after validating my app I get a NullPointerException error so I change back the same line to this
return new AuthorizationCodeInstalledApp(flow, receiver).authorize("myemail#gmail.com");
and if it doesn't work (I guess that's a cache problem)
to this
return new AuthorizationCodeInstalledApp(flow, receiver).authorize("me");
don't ask me why but that did work.
Still any more detailed infos about this behaviour is welcome.

Related

Gmail API for JAVA - Eclipse project - Read new mails and download attachments

I work on student project where I have to use GMAIL API (Java), to connect to mail server, get new messages and download attachments if there is any. I already done this with JavaMail API, but mail server that should use app doesn't accept IMAP and POP3 protocols, it has own web service.
I have two problems.
I have troubles even with starting project in eclipse.
First, I have no idea how to set up connection with mail server (for example gmail account) and how to provide username and password. I saw that gmail api for authorization uses Oauth2.
Second one is maybe easier to solve. When I have established connection to mail server, how to fetch unseen mails and download attachments?
I guess that second part I could do on my own, but without
connection its even useless to try.
I was reading official documentation for few days and I am quite confused.
(Maybe if you have some code sample or similar example it would be nice)
EDIT 1:
Done that, now I have this error
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
.setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH)))
.setAccessType("offline")
.build();
setAccessType is not found in com.google.api.client.auth.oauth2.AuthorizationCodeFlow.Builder
All Imports from Quickstart are there.
EDIT 2:
I am quite confused now
Exception in thread "main" java.io.FileNotFoundException: Resource not found: /resources/credentials.json
Line:
private static final String CREDENTIALS_FILE_PATH = "/resources/credentials.json";
File&Folder organization
When I check System.out.print(System.getProperty("user.dir"));
I get F:\NBS\eclipse\FetchMail
EDIT 3:
Deleted all files and started over again but eclipse still looks blind
File not found
EDIT 4:
Tested method .getResourceAsStream(path) inside Main method and it finds credential.json inside \eclipse\FetchMail or any other file i want
Then moved file to \eclipse\FetchMail\resources and call .getResourceAsStream("/resources/credentials.json") also finds file.
But when try this in getCredentials method from Quickstart, there is FileNotFound exception.
private static final String CREDENTIALS_FILE_PATH = "/resources/credentials.json";
private static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT) throws IOException {
// Load client secrets.
InputStream in = Main.class.getResourceAsStream(CREDENTIALS_FILE_PATH);
if (in == null) {
throw new FileNotFoundException("Resource not found: " + CREDENTIALS_FILE_PATH);
}
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));
// Build flow and trigger user authorization request.
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
.setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH)))
.setAccessType("offline")
.build();
LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build();
return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
}
Google uses Oauth2 to authorize and authenticate accounts in order to use the Google APIs. To do so, follow the next steps:
In your Google Developer Console, click on Credentials.
Click on CREATE CREDENTIALS and Select Oauth Client Id
In this case, choose Desktop App
Set any Name and click on Create
You will get a screen with the Client ID and the Client Secret. These keys will authorize your account to use the APIs. They are stored in the credentials file. You don't need to do anything with them, so close the window.
Download the credential's JSON file.
In the Quickstart code, you will notice this line:
private static final String CREDENTIALS_FILE_PATH = "/credentials.json";
You have to set the path of your credentials file. Change the name of the file to yours.
When running the code for the first time, you will get a link to accept the permissions specified in the Scopes. This will generate an Access token which allows the application to access the API without asking for human interaction until it expires.
Finally solved, changed .getResourceAsStream with FileInputStream(path) constructor and it works.

Google service account authorization error

I'm trying to use Google APIs with a service account.
Originally I used a normal project account with credentials and I managed to query the APIs. To use a service account, I modified the code to skip the browser auth flow and only use the service account credentials. It seems that the same request that used to work with the normal project credentials now give an error without clarifying much in the message. It goes as follows:
directoryService = new GoogleService().buildDirectoryService();
Users result = directoryService.users().list()
.setCustomer("my_customer")
.setOrderBy("email")
.execute();
public Directory buildDirectoryService() throws GeneralSecurityException, IOException {
final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
GoogleCredential credential = GoogleCredential
.fromStream(new FileInputStream("src/main/resources/newest_service_account_creds.json"))
.createScoped(SCOPES);
return new Directory.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential)
.setApplicationName(APPLICATION_NAME)
.setHttpRequestInitializer(credential).build();
}
Unfortunately the error message is not saying much:
Exception in thread "main" com.google.api.client.googleapis.json.GoogleJsonResponseException: 400 Bad Request
{
"code" : 400,
"errors" : [ {
"domain" : "global",
"message" : "Invalid Input",
"reason" : "invalid"
} ],
"message" : "Invalid Input"
}
at com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:146)
at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:113)
at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:40)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest$1.interceptResponse(AbstractGoogleClientRequest.java:321)
at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:1108)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:419)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:352)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.execute(AbstractGoogleClientRequest.java:469)
at GetDrivePermissions.getFiles(GetDrivePermissions.java:35)
at Main.main(Main.java:7)
I repeated the process two times to make sure I didn't miss anything, including creating a new project, enabling the APIs, adding a service account, adding and enabling domain-wide delegation and obtaining a credentials json file, also adding access to the app in App Access Control in the admin UI. But calling
directoryService.users().list().setCustomer("my_customer").setOrderBy("email").execute();
service still returns the above error message.
The error message refers to "Invalid input", but the request used to work before, with normal application credentials. So the problem must lay not in how I call the Directory service but with my authentication, the only difference between the working and the non-working version.
Since this worked with using the project credentials, there must be something wrong with how I use the service account, but I can't find the error even after repeating the whole process two times. Based on the guides I found about service accounts, this is supposed to work. The credential object is created and it looks proper.
What can I look into next?
Thnaks for any insight in advance.
I changed this part back to use the application's own credentials. I have another call that uses the service account credentials and that works, so this is a workaround for now.

Google Gmail API request with service credential returning 400 using Java SDK

I'm trying to access my own personal Gmail account using the Gmail API with service account credentials and the Java SDK (v1.22.0). I've generated the credentials and saved the json file locally, then tried running this:
public class GmailAPITest {
public static void main(String[] args) throws Exception {
JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
HttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
String filepath = "/path/to/my/creds.json";
GoogleCredential credential = GoogleCredential.fromStream(new FileInputStream(filepath))
.createScoped(Collections.singleton(GmailScopes.GMAIL_READONLY));
Gmail service = new Gmail.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential)
.setApplicationName("MyApp")
.build();
String user = "me";
ListLabelsResponse listResponse = service.users().labels().list(user).execute();
List<Label> labels = listResponse.getLabels();
if (labels.size() == 0) {
System.out.println("No labels found.");
} else {
System.out.println("Labels:");
for (Label label : labels) {
System.out.printf("- %s\n", label.getName());
}
}
}
I get a 400 response like this:
{
"code" : 400,
"errors" : [ {
"domain" : "global",
"message" : "Bad Request",
"reason" : "failedPrecondition"
} ],
"message" : "Bad Request"
}
at com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:146)
at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:113)
at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:40)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest$1.interceptResponse(AbstractGoogleClientRequest.java:321)
at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:1065)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:419)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:352)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.execute(AbstractGoogleClientRequest.java:469)
at ie.test.GmailAPITest.main(GmailAPITest.java:34)
I've tried enabling Domain-wide Delegation on the service credentials, and I've tried changing the user "me" to the email address of my account, but it's the same result. I've also looked at a number of questions with similar problems but I haven't found any of them useful. Am I missing something?
The Gmail API is enabled from the developer console and from there I can see all my failed 4xx requests.
I should also mention that I've created another test using Python, which does exactly the same thing using the same service credentials, and it fails with the same 400 error. I've tried creating a new key (json) under the same security credential and tried using that, but it's the same result.
I've been able to get things working, but only by using the alternative oAuth sign-in process i.e. being prompted to allow/deny access from a web browser, which then saves some oAuth access tokens to a file for future user. This is not ideal, because I'm going to have to constantly renew these tokens. The service credential was presumably created to avoid that exact problem. I'm still very interested in finding out why it's not working for me.
Service account domain wide delegation is for G Suite accounts only and requires the G Suite admin to authorize the service account client id to have access to the necessary scopes. If you are working with consumer Gmail accounts (#gmail.com address) or you don't have G Suite admin approval then you should use normal OAuth flow like you have working now.

Gmail API setting up service account and reading/sending mails on behalf of user

I have a SSL secured jetty web server whose task is to read/send emails on behalf of the user. For reading pub/sub is preferred, instead of pull. Have followed these steps from developers.google.com
Created a project in console, enabled Gmail api, created service account with google apps domain-wide delegation downloaded both json and p12 private key
Created OAuth cliend-id by selecting web application gave it a name and added authorized redirect URI to one of the web server api end point
Grant publish right to the topic using console api
Created topic in pub-sub and subscribed with an HTTPs endpoint. Manually checked using console API to publish message and received successfully
Now all that left to do is use java client library to utilize service account credentials and talk to gmail api's on behalf of the user (have collected auth & refresh token from the users for the topic)
Steps tried.
Using the private key downloaded (p12) tried creating credential and for sample retrieving labels of the mail
HttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
String serviceAccount = "service-account#myproject.iam.gserviceaccount.com";
Credential credential = GoogleCredential.Builder().setTransport(HTTP_TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(serviceAccount)
.setServiceAccountPrivateKeyFromP12File(new File("/path-to-key-file.p12"))
.setServiceAccountScopes(Collections.singleton(GmailScopes.GMAIL_LABELS))
.build();
Gmail service = new Gmail.Builder(HTTP_TRANSPORT, JSON_FACTORY, cred).setApplicationName("web-application-name-created-via-console").build();
String user = "me"; //Tried direct email id also
service.users().labels().list(user).execute();
Response,
{
"code" : 400,
"errors" : [ {
"domain" : "global",
"message" : "Bad Request",
"reason" : "failedPrecondition"
} ],
"message" : "Bad Request"
}
Note: The coding part is tried in local server and not the one which has SSL. I suppose that shouldn't be an issue as i am having all the necessary key files to access api.
Please, let me know if i am missing something.
Initially I was using the OAuth 2.0 client IDs for my web application to fetch messages from a specific gmail account. And then I realized that consent prompting was not at all required for my application and that's when I stumbled upon the google service accounts that eliminated the consent prompting and storing of the authorization tokens. I did attempt the very same code posted above and hit upon the exact same response posted "message" : "Bad Request", "reason" : "failedPrecondition".
To resolve the issue added the .setServiceAccountUser(googleServiceAccountUser) where googleServiceAccountUser is set to my google email id (xxx#gmail.com) from which my application was attempting to read messages. In your code snippet provided the call ".setServiceAccountUser()" is missing and adding it may help you resolve the issue.
private static Credential authorize() throws Exception {
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(googleServiceAccountId)
.setServiceAccountPrivateKeyFromP12File(new File(privatekeyFile))
.setServiceAccountScopes(Collections.singleton(GmailScopes.MAIL_GOOGLE_COM))
.setServiceAccountUser(googleServiceAccountUser)
.build();
return credential;
}

Google Analytics 3.0 auth flow

EDIT: Originally this question asked how I could authenticate with the Google Analytics API using only my API key. As vlatko pointed out, this isn't possible. Now I'm just focused on getting OAuth2 to work. I will be trying vlatko's suggestions when I get a chance and will update the question. In the meantime, feel free to contribute answers with anything you think I'm missing.
ORIGINAL QUESTION:
I'm trying to make requests to the Google Analytics API. I'm walking through the Hello Analytics tutorial trying to replicate the steps. Whatever I try, I can't seem to authenticate succesfully.
The tutorial says the following:
Open the file you created named HelloAnalyticsApi.java and add the
following method:
private static Analytics initializeAnalytics() throws Exception {
// Authorization.
Credential credential = OAuth2Native.authorize(
HTTP_TRANSPORT, JSON_FACTORY, new LocalServerReceiver(),
Arrays.asList(AnalyticsScopes.ANALYTICS_READONLY));
// Set up and return Google Analytics API client.
return Analytics.builder(HTTP_TRANSPORT, JSON_FACTORY)
.setApplicationName("Google-Analytics-Hello-Analytics-API-Sample")
.setHttpRequestInitializer(credential)
.build();
}
When a user encounters this script, the application will attempt to
open the default browser and navigate the user to a URL hosted on
google.com. At this point, the user will be prompted to login and
grant the application access to their data. Once granted, the
application will attempt to read a code from the browser window, then
close the window.
The difference is that I'm trying to do this with a servlet application, and I want to use simple API access with an API key (rather than an OAuth 2.0 client ID). I know that OAuth 2.0 is recommended, but I only need to access data that I own and want to simplify the technical requirements. I based this decision on this page, which says:
An API key is a unique key that you generate using the Console. When
your application needs to call an API that's enabled in this project,
the application passes this key into all API requests as a key={API_key}
parameter. Use of this key does not require any user action or
consent, does not grant access to any account information, and is not
used for authorization.
If you are only calling APIs that do not require user data, such as
the Google Custom Search API, then API keys may be simpler to
implement. However, if your application already uses an OAuth 2.0
access token, then there is no need to generate an API key as well. In
fact, Google ignores passed API keys if an OAuth 2.0 access token is
already associated with the corresponding project.
I can't find many code examples of auth flow just using the API key - most everything I've found shows using the client ID with the downloaded .p12 file, for example the GoogleCredential javadoc. The one example application I could find was Google's Books Sample app. Anyway, here's what I tried (mimicking the first request in the tutorial, which gets a list of the accounts from the management API):
Analytics analytics =
new Analytics.Builder(httpTransport, jsonFactory, null)
.setApplicationName("Dev API Access")
.build();
Management.Accounts.List list =
analytics.management().accounts().list().setKey(apiKey);
Accounts accounts = list.execute();
Where "Dev API Access" is the "Name" field in my API console dashboard. The API key is a server key restricted to my IP address. This fails with the following response:
401 Unauthorized
{
"code": 401,
"errors": [
{
"domain": "global",
"location": "Authorization",
"locationType": "header",
"message": "Login Required",
"reason": "required"
}
],
"message": "Login Required"
}
I also tried this:
Analytics analytics =
new Analytics.Builder(httpTransport, jsonFactory, null)
.setApplicationName("Dev API Access")
.setGoogleClientRequestInitializer(new AnalyticsRequestInitializer(apiKey))
.build();
Management.Accounts.List list = analytics.management().accounts().list();
Accounts accounts = list.execute();
Which shows the same error. What am I doing wrong here? Is OAuth2 required for analytics calls? If so, why does just using the API key work in the Books Sample app?
Moving on, I went ahead and tried OAuth2 anyway - I created a client ID and downloaded the .p12 private key file. But I couldn't get that working either. Here's what I tried:
Credential credential =
new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountId(serviceAccountId)
.setServiceAccountScopes(AnalyticsScopes.ANALYTICS_READONLY)
.setServiceAccountPrivateKeyFromP12File(new File(p12FilePath))
.setServiceAccountUser(serviceAccountUser)
.build();
Analytics analytics =
new Analytics.Builder(httpTransport, jsonFactory, credential)
.setApplicationName("Dev API Access")
.build();
Management.Accounts.List list = analytics.management().accounts().list();
Accounts accounts = list.execute();
Where serviceAccountId is the email address of the Google account owning the project and serviceAccountUser is the email address listed on the generated client ID. This fails with the following:
400 Bad Request
{
"error": "invalid_grant"
}
What does "invalid grant" mean, and how do I successfully authenticate (ideally without OAuth2)?
To answer your first question: in general, OAuth2.0 is used for authorized access to user's private data, so getting user consent and obtaining an access token is required. In the case with Google Books API, however, if you're accessing public data, there is no need for end user consent so an API key is sufficient. If you try accessing non public data with the Books API, you'll still need an OAuth2 token.
The good news for your case is that even with OAuth2, you can bypass user involvement and streamline your flow with Service Accounts - assuming your application has access to the API. There is a way to set that up for the Analytics API, explained here (check the steps in the Service Accounts section). I think you are on the right track with your Credential builder, but I don't think you need to set the service account user in there, since you are not doing any user impersonation.
vlatko's answer got me on the right track. The issue turned out to be that I was confusing the owner email address with the service account email address. For example, I was doing the following:
Credential credential =
new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountId("owneremail#gmail.com") //wrong
.setServiceAccountScopes(AnalyticsScopes.ANALYTICS_READONLY)
.setServiceAccountPrivateKeyFromP12File(new File(p12FilePath))
.setServiceAccountUser("xxx#developer.gserviceaccount.com") //unnecessary
.build();
When I needed to do this:
Credential credential =
new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountId("xxx#developer.gserviceaccount.com")
.setServiceAccountScopes(AnalyticsScopes.ANALYTICS_READONLY)
.setServiceAccountPrivateKeyFromP12File(new File(p12FilePath))
.build();
Also, I had added owneremail#gmail.com as a user on my Analytics application - this similarly needed to be the service account email instead.

Categories

Resources