Analytics API from app engine with AppIdentityCredential insufficient permissions - java

I am attempting to hit Google analytics API from my java app engine module.
I have the following code to retrieve the credential.
public HttpRequestInitializer getCredentials() throws GeneralSecurityException, IOException {
HttpRequestInitializer httpRequestInitializer;
if (com.google.appengine.api.utils.SystemProperty.environment.value() == com.google.appengine.api.utils.SystemProperty.Environment.Value.Development) {
String certificate = "/token.p12";
File privateKey = null;
try {
privateKey = new File(getClass().getResource(certificate).toURI());
} catch (URISyntaxException e) {
log.log(Level.SEVERE, e.getMessage(), e);
}
httpRequestInitializer = new GoogleCredential.Builder()
.setTransport(new NetHttpTransport())
.setJsonFactory(new JacksonFactory())
.setServiceAccountId("XXXX#developer.gserviceaccount.com")
.setServiceAccountPrivateKeyFromP12File(privateKey)
.setServiceAccountScopes(Collections.singleton(AnalyticsScopes.ANALYTICS_READONLY))
.build();
} else {
httpRequestInitializer = new AppIdentityCredential(Arrays.asList(AnalyticsScopes.ANALYTICS_READONLY));
}
return httpRequestInitializer;
}
I have enabled the analytics api for the project in the cloud dev console. In Google analytics I have given the service account read access. Google analytics is using the same google account as I am using for google cloud.
Anyway, when using the app engine dev server this code works and I can hit the api (using the client library). However, when run from app engine itself and it uses AppIdentityCredential I am getting the following exception when attempting to hit the API.
com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 OK
{
"code" : 403,
"errors" : [ {
"domain" : "global",
"message" : "User does not have any Google Analytics account.",
"reason" : "insufficientPermissions"
} ],
"message" : "User does not have any Google Analytics account."
}
I would assume that AppIdentityCredential authenticates as the user who owns the app engine project, but I guess it doesn't?
I would like to use AppIdentityCredential as I understand that this is recommended for production instances.
How can I use AppIdentityCredential and access the analytics api with it?

Uh ok I figured it out.
The app engine project is tied to the identity/account of PROJECTNAME#appspot.gserviceaccount.com . Giving this account access in analytics settings allows the app engine code to access it using AppIdentityCredential

Related

How to log in with a Google Service account in the Google Cloud?

I created a service account and generated a json key. I want to execute a get request to get a project.
I using https://cloud.google.com/dns/docs/reference/v1/projects/get
public static Dns createDnsService() throws IOException, GeneralSecurityException {
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
String jsonPath = "testdns-320312-136950c1074b.json";
FileInputStream stream = new FileInputStream(jsonPath);
GoogleCredential credential = GoogleCredential.fromStream(stream);
if (credential.createScopedRequired()) {
credential =credential.createScoped(Arrays.asList("https://www.googleapis.com/auth/cloud-platform"));
}
return new Dns.Builder(httpTransport, jsonFactory, credential)
.setApplicationName("Google-DnsSample/0.1")
.build();
}
When executing the code, I get error:
Exception in thread "main" com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden
GET https://dns.googleapis.com/dns/v1/projects/testdns-320312
{
"code" : 403,
"errors" : [ {
"domain" : "global",
"message" : "Forbidden",
"reason" : "forbidden"
} ],
"message" : "Forbidden"
}
#John Hanley is right - you lack proper permissions.
If you didn't assign any role to a service account after you created it you can't use it to do anything.
You need a roles/dns.admin role to be able to administer all the DNS records in your project.
You can do this with the following steps:
In the Google Cloud Console, go to the IAM page.
Go to the IAM page
Select your project from the top pull-down menu.
Click Add.
In New members, enter the email address of a new member.
Select the desired role from the drop-down menu.
Click Save.
Verify that the member is listed with the role that you granted.
Granting DNS admin role to this account will be the easiest way of getting control over Cloud DNS section of your project. If you need more fine-grained / limited access then you can create your custom role and assign it to this account instead of the basic one.
Here's more info about granting roles in GCP that you may find useful.
Very similar question was also answered here.

Create Google Drive channel for PUSH notification using Service Account

I'm trying to create a channel in order to receive push notifications when an edit is made on a Google Spreadsheet. The code will be executed on an App Engine Standard Java8 project.
Because a channel can last a maximum of 24 hours, I'm creating a cron that will renew the channel every day.
Here is my configuration:
I created a service account: xxxx#yyyy.iam.gserviceaccount.com
I verified the domain: yyyy.appspot.com (in Webmaster Tools)
I added the domain to cloud console (section API & Services -> Domain Verification)
I created an endpoint which will handle push notification: https://yyyy.appspot.com/api/drive/push
The sheetId is: foo_bar (copied by the Spreadsheet browser url itself, like https://docs.google.com/spreadsheets/d/foo_bar/edit)
The service account is Can Edit on the Sheet
With the Java library google-api-services-drive:v3-rev102-1.23.0 I created this code
public static void main(String[] args) throws IOException {
String spreadsheetId = "foo_bar";
InputStream json = GoogleDriveApi.class.getClassLoader().getResourceAsStream("bar.json");
GoogleCredential credential = GoogleCredential.fromStream(json, new NetHttpTransport(), new JacksonFactory());
if (credential.createScopedRequired()) {
credential = credential.createScoped(Collections.singleton("https://www.googleapis.com/auth/drive.appdata"));
}
Drive service = new Drive.Builder(new NetHttpTransport(), new JacksonFactory(), credential).build();
Channel channel = new Channel();
channel.setAddress("https://example.com/api/drive/push");
channel.setType("web_hook");
channel.setId(UUID.randomUUID().toString());
Watch action = service.files().watch(spreadsheetId, channel);
System.out.println(action.execute().toPrettyString());
}
When I execute this code I get this error:
com.google.api.client.googleapis.json.GoogleJsonResponseException: 404 Not Found
{
"code" : 404,
"errors" : [ {
"domain" : "global",
"location" : "fileId",
"locationType" : "parameter",
"message" : "File not found: foo_bar.",
"reason" : "notFound"
} ],
"message" : "File not found: foo_bar."
}
From documentation I quote
Each notification channel is associated both with a particular user
and a particular resource (or set of resources). A watch request will
not be successful unless the current user or service account owns or
has permission to access this resource.
In my case Service Account does not OWNS the resource (Spreadsheet) but indeed has permission on it (Can Edit permission, non only Can View)
Can a service account be used to create a channel on Google Drive?
file.watch API requires https://www.googleapis.com/auth/drive scope, in the main example I was using only https://www.googleapis.com/auth/drive.appdata
Using the following line the example works fine
credential = credential.createScoped(Collections.singleton("https://www.googleapis.com/auth/drive"));

Get access google spreadsheet (saved into google drive account) from a java google app engine (servlet)

I really stuck here. I want to access to a google spreadsheet from an google app engine (with java, using servlet because the idea is read the information from that spreadsheet, saved in my google drive account, and shows via "jsp" to a limited users number, here in my company).
Of course i've already create my project in the "google cloud developer console" i got the "project id" fourth-case-XXXX, etc . Until this step I understood everything and went well.
I´ve been looking thousands of example here , on google, etc.For everything I read I understand that: I must to create a "OAuth2 credential" (reason why i created OAuth2 credential in the "Api and authentication" in my developer console of course (i got a json with a auth_uri, client_secret, client_id, etc, etc).
From what I've read just need the client_secret and client_id. But following this tutorial (link) i got this error "oauth_token does not exist.". In other tutorial i´v read that is not necessary use "OAuth2" in app engine. I am very dizzy.
The only that i want is a simple java servlet (nothing complex, following good practices, I do not care) that read data from google spreadsheet (saved into my google drive account) and showing through a "jsp", at least I am content to accept for receiving the spreadsheet data on my servlet and later I will imagine how to show
I´m ussing eclipse luna, i got installed "gdata java api", "google app engine as localhost", etc (all properly installed, running without errors). to create the ambient I followed this tutorial
Some questions:
1-The spreadsheet need to be published? (menu: file->publish to the web). to get acces from an app engine?
2-To test my code (and look if i got access to the spreadhsheet) in mandatory Uploading to Google App Engine (http://.appspot.com/guestbook) or i can try with "localhost"?
i will upload my code, attached image etc. I apologize for my nasty code but now I need to solve this issue
Thanks all
public void callingSpreadsheetTest2() throws IOException {
System.out.println("callingSpreadsheetTest2");
HttpTransport transport = new NetHttpTransport();
JacksonFactory jsonFactory = new JacksonFactory();
GoogleOAuthParameters oAuthParameters = new GoogleOAuthParameters();
oAuthParameters.setOAuthConsumerKey(CLIENT_ID);
oAuthParameters.setOAuthConsumerSecret(CLIENT_SECRET);
OAuthHmacSha1Signer signer = new OAuthHmacSha1Signer();
GoogleOAuthHelper oAuthHelper = new GoogleOAuthHelper(signer);
oAuthParameters.setScope(SCOPES);
try{
oAuthHelper.getUnauthorizedRequestToken(oAuthParameters);
}catch (OAuthException e){
e.printStackTrace();
}
String requestUrl = oAuthHelper.createUserAuthorizationUrl(oAuthParameters);
System.out.println(requestUrl);
System.out.println("Please visit the URL above to authorize your OAuth "
+ "request token. Once that is complete, press any key to "
+ "continue...");
try{
System.in.read();
}catch(IOException e){
e.printStackTrace();
}
oAuthParameters.setOAuthType(OAuthType.TWO_LEGGED_OAUTH);
String token = null;
try{
token = oAuthHelper.getAccessToken(oAuthParameters);
}catch(OAuthException e){
e.printStackTrace(); //---->Attention: HERE I GOT toke=null (oauth_token does not exist.)
}
System.out.println("OAuth Access Token: " + token);
System.out.println();
URL feedUrl = null;
try{
feedUrl = new URL(SPREADSHEET_URL);
}catch(MalformedURLException e){
e.printStackTrace();
}
SpreadsheetService spreadsheetService = new SpreadsheetService("GAppEngineProj");
try{
spreadsheetService.setOAuthCredentials(oAuthParameters, signer);
}catch(OAuthException e){
e.printStackTrace();
}
try {
SpreadsheetFeed feed = spreadsheetService.getFeed(feedUrl, SpreadsheetFeed.class);
List<SpreadsheetEntry> spreadsheets = feed.getEntries();
if(spreadsheets != null) {
for (SpreadsheetEntry spreadsheet : spreadsheets) {
System.out.println(spreadsheet.getTitle().getPlainText());
}
}
} catch (ServiceException e) {
e.printStackTrace();
}
Also my class "com.google.gdata.client.spreadsheet.SpreadsheetService" NOT have the method setOAuth2Credentials only [service.setOAuthCredentials(parameters, signer);]
Once again thanks!!
You're using code for the OAuth1 protocol, which is deprecated. You should use OAuth2 instead.
An App Engine application comes with a very convenient AppIdentityService (documentation) which allows your application to generate OAuth2 tokens as if the application had an email in the format your-project-id#appspot.gserviceaccount.com.
All you have to do is share your spreadsheet with this email using the Google Drive / Google Spreadsheets UI. In your case the email would be fourth-case-XXXX#appspot.gserviceaccount.com
Then, in the code, do this :
AppIdentityService appIdentity = AppIdentityServiceFactory.getAppIdentityService();
String accessToken = appIdentityService.getAccessToken(Collections.singleton(SPREADSHEET_SCOPE)).getAccessToken();
GoogleCredential googleCredential = new GoogleCredential();
googleCredential.setAccessToken(accessToken);
spreadsheetService = new SpreadsheetService("MyApp");
spreadsheetService.setOAuth2Credentials(buildCredential());
Then you are ready to use your spreadsheetService to read data.
NB : this code was written with the GData library version 1.47.1 .

Youtube Video id by using Service account - got error 403 response

I’m developing Java code to retrieve Video Id for the corresponding asset id by using service account authentication.I have followed below mentioned steps.
Step 1: A Java program is written to retrieve the access token from Google authentication using service account.
the code is for getting access_token:
String EmailId = "XXXXXXXXXXX#developer.gserviceaccount.com";
//passing Scope
#SuppressWarnings({ "unchecked", "rawtypes" })
List<String>scops = new <String>ArrayList();
scops.add("https://www.googleapis.com/auth/youtubepartner");
final HttpTransport TRANSPORT = new NetHttpTransport();
final JsonFactory JSON_FACTORY = new JacksonFactory();
// Create a listener for automatic refresh OAuthAccessToken
List<CredentialRefreshListener> list = new ArrayList<CredentialRefreshListener>();
list.add(new CredentialRefreshListener() {
#Override
public void onTokenResponse(Credential credential,
TokenResponse tokenResponse) throws IOException {
System.out.println(tokenResponse.toPrettyString());
}
public void onTokenErrorResponse(Credential credential,
TokenErrorResponse tokenErrorResponse)
throws IOException {
System.err.println("Error: "
+ tokenErrorResponse.toPrettyString());
}
});
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(EmailId)
.setServiceAccountScopes(scops)
.setServiceAccountPrivateKeyFromP12File(new File("test.p12"))
.setRefreshListeners(list)
.build();
credential.refreshToken();
Step 2: The access token that is obtained in step 1 is passed as an argument in the below mentioned URL to get Youtube video id
Sample url is:
https://www.googleapis.com/youtube/partner/v1/claimSearch?assetId=xxxxxxxxx&onBehalfOfContentOwner=xxxxxxxx&status=active&access_token=ya29.-gCmzBHciDghrj2EDtBn1Vx0MV38pNLZTvqfwOyG0hNJCj75nsCBA5zaxmP1sr7UqI7ZrYI3AIZstA
I’m getting 403 error code in step 2.
{
"error": {
"errors": [
{
"domain": "usageLimits",
"reason": "accessNotConfigured",
"message": "Access Not Configured. The API is not enabled for your project, or there is a per-IP or per-Referer restriction configured on your API key and the request does not match these restrictions. Please use the Google Developers Console to update your configuration.",
"extendedHelp": "https://console.developers.google.com"
}
],
"code": 403,
"message": "Access Not Configured. The API is not enabled for your project, or there is a per-IP or per-Referer restriction configured on your API key and the request does not match these restrictions. Please use the Google Developers Console to update your configuration."
}
}
already enabled “GOOGLE COULD JSON API” and “YOUTUBE DATA API”.
Could any one please help me to resolve this issue?
I know from working on a previous project that you need to enable the API in the Google console. I would try that first.
https://code.google.com/apis/console
You try to use Youtube's Content ID API endpoint /youtube/partner/v1/claimSearch.
This endpoint is not part of the Youtube Data API and you can only access it if you participate in the "Youtube Partner Program". Otherwise it is not even listed in the Developer's Console.
Further information here: https://developers.google.com/youtube/partner/
Note: The YouTube Content ID API is intended for use by YouTube content partners and is not accessible to all developers or to all YouTube users. If you do not see the YouTube Content ID API as one of the services listed in the Google Developers Console, see www.youtube.com/partner to learn more about the YouTube Partner Program.

Gmail Java API Using GAE Service account

I am trying to read gmail messages using GAE Service account. The code similar to below works well to read Google calendar but fails for gmail. I have only one user in the domain (who is also the admin account). I am trying to access the gmail of that user). Any help on why my code fails would be great.
I have delegated domain wide authority for the following scopes.
Email (Read/Write/Send) https://mail.google.com/
https://www.googleapis.com/auth/admin.directory.user.readonly
Calendar (Read-Write) https://www.googleapis.com/auth/calendar
https://www.googleapis.com/auth/drive
https://www.googleapis.com/auth/userinfo.email
https://www.googleapis.com/auth/userinfo.profile
I have enabled below APIs in the Google App Engine:
Admin SDK
Calendar API
Drive API
Gmail API
Google Cloud SQL
Google Cloud Storage
Google Cloud Storage JSON API
Below is the code that creates gmail service:
// Create HTTP transport
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
// Create JsonFactory using com.google.api.client.json.jackson2.JacksonFactory
JsonFactory jsonFactory = new JacksonFactory();
// Get service accont (contain service account email and P12 file name)
BaseGoogleService.GoogleServiceAccount serviceAccount = getGoogleServiceAccount();
//load private key from class path to File object.
Resource resource = appContext.getResource("classpath:"+serviceAccount.getServiceAccountPrivateKeyFile());
File privateKeyFile = resource.getFile();
// Create Google credential for service account
List<String> accountScopes = new ArrayList<String>();
accountScopes.add(GmailScopes.MAIL_GOOGLE_COM);
Credential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountId(serviceAccount.getServiceAccountId())
.setServiceAccountScopes(accountScopes)
.setServiceAccountPrivateKeyFromP12File(privateKeyFile)
.build();
// Create Gmail client
Gmail gmailClient = new Gmail.Builder(httpTransport, jsonFactory, credential)
.build();
Code that reads gmail messages:
ListMessagesResponse response = gmailClient.users().messages().list(userEmail).setQ(query).execute()
I am getting below error when I execute this code
com.google.api.client.googleapis.json.GoogleJsonResponseException: 500 OK
{
"code" : 500,
"errors" : [ {
"domain" : "global",
"message" : "Backend Error",
"reason" : "backendError"
} ],
"message" : "Backend Error"
}
at com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:145)
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:312)
at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:1049)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:410)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:343)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.execute(AbstractGoogleClientRequest.java:460)
I was able to make it work.
Two things:
Have to add ".setServiceAccountUser(userEmail)" while building GoogleCredential. userEmail represents the email id to impersonate.
This cannot be done for admin account. I had to add a user to the domain and access that user's gmail.

Categories