I am using Google Forms API to get form responses using service account.
package com.form.api.poc;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Arrays;
import java.util.Objects;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.forms.v1.Forms;
import com.google.api.services.forms.v1.FormsScopes;
import com.google.api.services.forms.v1.model.ListFormResponsesResponse;
import com.google.auth.oauth2.GoogleCredentials;
public class Test1 {
private static final String APPLICATION_NAME = "google-form-api-project";
private static Forms formsService;
static {
try {
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
formsService = new Forms.Builder(GoogleNetHttpTransport.newTrustedTransport(),
jsonFactory, null)
.setApplicationName(APPLICATION_NAME).build();
} catch (GeneralSecurityException | IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
ListFormResponsesResponse response = readResponses("form_id_jdnfka_123b34$Q%#nknk", getAccessToken());
System.out.println(response.toPrettyString());
}
public static String getAccessToken() throws IOException {
GoogleCredentials credential = GoogleCredentials.fromStream(Objects.requireNonNull(
Test1.class.getResourceAsStream("/serviceaccount_cred.json"))).createScoped(Arrays.asList(FormsScopes.FORMS_BODY_READONLY));
return credential.refreshAccessToken().getTokenValue();
}
private static ListFormResponsesResponse readResponses(String formId, String token) throws IOException {
return formsService.forms().responses().list(formId).setOauthToken(token).execute();
}
}
But, I am getting following error as a response:
GET https://forms.googleapis.com/v1/forms/form_id_jdnfka_123b34$Q%#nknk/responses?oauth_token=token_id..........
{
"code" : 403,
"details" : [ {
"#type" : "type.googleapis.com/google.rpc.ErrorInfo",
"reason" : "ACCESS_TOKEN_SCOPE_INSUFFICIENT"
} ],
"errors" : [ {
"domain" : "global",
"message" : "Insufficient Permission",
"reason" : "insufficientPermissions"
} ],
"message" : "Request had insufficient authentication scopes.",
"status" : "PERMISSION_DENIED"
}
I have followed the documentation provided at the following pages:
using-service-account
Delegating-domain wide authority
But, not sure, what is wrong, any help would be appreciated.
Thank you in advance!
Two things has to be done here, first, use FormsScopes .FORMS_RESPONSES_READONLY for responses and the service account email address shall have permission on the given form_id.
Yet, not sure, if its the right way to go ahead, maybe if executed locally thats the reason it requires the permission, maybe if the above program once executed in some GC environment, then the permission need not be given explicitly to the service account on the form.
Related
I have created one poc using Gmail API which read all email and print on console. I have take refrence from Gmail api Java quickstart.
I have follows all the steps like create project, Gmail API enable, OAuth credentials in google cloud console
The problem is, when I run the code all the labels are printed successfully but I'm not able to read mail messages. Some an error are getting which are below:
> Task :GmailQuickstart.main()
Labels:
- CHAT
- SENT
- INBOX
- IMPORTANT
- TRASH
- DRAFT
- SPAM
- CATEGORY_FORUMS
- CATEGORY_UPDATES
- CATEGORY_PERSONAL
- CATEGORY_PROMOTIONS
- CATEGORY_SOCIAL
- STARRED
- UNREAD
Exception in thread "main" com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden
GET https://gmail.googleapis.com/gmail/v1/users/me/messages
{
"code": 403,
"details": [
{
"#type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "ACCESS_TOKEN_SCOPE_INSUFFICIENT"
}
],
"errors": [
{
"domain": "global",
"message": "Insufficient Permission",
"reason": "insufficientPermissions"
}
],
"message": "Request had insufficient authentication scopes.",
"status": "PERMISSION_DENIED"
}
at com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:146)
at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:118)
at com.google.api.client.googleapis.services.json.AbstractGoogleJsonClientRequest.newExceptionOnError(AbstractGoogleJsonClientRequest.java:37)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest$1.interceptResponse(AbstractGoogleClientRequest.java:439)
at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:1111)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:525)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.executeUnparsed(AbstractGoogleClientRequest.java:466)
at com.google.api.client.googleapis.services.AbstractGoogleClientRequest.execute(AbstractGoogleClientRequest.java:576)
at GmailQuickstart.main(GmailQuickstart.java:80)
> Task :GmailQuickstart.main() FAILED
Execution failed for task ':GmailQuickstart.main()'.
> Process 'command '/home/bhautik/Downloads/jdk-11.0.15.1_linux-x64_bin/data/usr/lib/jvm/jdk-11/bin/java'' finished with non-zero exit value 1
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
And my code was below:
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.gson.GsonFactory;
import com.google.api.client.util.store.FileDataStoreFactory;
import com.google.api.services.gmail.Gmail;
import com.google.api.services.gmail.GmailScopes;
import com.google.api.services.gmail.model.Label;
import com.google.api.services.gmail.model.ListLabelsResponse;
import com.google.api.services.gmail.model.ListMessagesResponse;
import com.google.api.services.gmail.model.Message;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/* class to demonstrate use of Gmail list labels API */
public class GmailQuickstart {
private static final String APPLICATION_NAME = "Gmail API Java Quickstart";
private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
private static final String TOKENS_DIRECTORY_PATH = "tokens";
private static final List<String> SCOPES = Collections.singletonList(GmailScopes.GMAIL_LABELS);
private static final String CREDENTIALS_FILE_PATH = "/credentials.json";
private static final String USER_ID = "me";
private static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT)
throws IOException {
// Load client secrets.
InputStream in = GmailQuickstart.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();
Credential credential = new AuthorizationCodeInstalledApp(flow, receiver).authorize("user");
//returns an authorized Credential object.
return credential;
}
public static void main(String... args) throws IOException, GeneralSecurityException {
// Build a new authorized API client service.
final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
Gmail service = new Gmail.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT))
.setApplicationName(APPLICATION_NAME)
.build();
// Print the labels in the user's account.
String user = "me";
ListLabelsResponse listResponse = service.users().labels().list(user).execute();
List<Label> labels = listResponse.getLabels();
if (labels.isEmpty()) {
System.out.println("No labels found.");
} else {
System.out.println("Labels:");
for (Label label : labels) {
System.out.printf("- %s\n", label.getName());
}
}
// Print the message
ListMessagesResponse response = service.users().messages().list(USER_ID).execute();
// List<Message> messages = response.getMessages();
List<Message> messages = new ArrayList<Message>();
while (response.getMessages() != null) {
messages.addAll(response.getMessages());
if (response.getNextPageToken() != null) {
String pageToken = response.getNextPageToken();
response = service.users().messages().list(USER_ID).setPageToken(pageToken).execute();
} else {
break;
}
}
for (Message message : messages) {
System.out.println(message.getId());
System.out.println(message.getPayload());
Message test = service.users().messages().get("me", message.getId()).execute();
System.out.println(test.getSnippet());
}
}
}
ACCESS_TOKEN_SCOPE_INSUFFICIENT is a very common error message. It comes from copying the example without understanding what its doing. This is googles fault for not explaining things better.
The quick start uses the lables.list method. This method runs on users private data so you used Oauth2 to request permission of the user to access their data.
The method can run with any of the following permissions being requested
Best practice is to only request the permissions you need. So the code is asking for the GmailScopes.GMAIL_LABELS permission which will only give you access to see the lables
Now to read a users messages the messges.listmethod you need to request permission with one of the following scopes
as you can see the label scope is not there. Thats because you need a higher level of permissions to access this data..
Solution:
Change the scope in your code to request one of the scopes needed for messgaes.list.
Then you need to reauthorize your application. YOu can do this in a few was.
delete the file found in TOKENS_DIRECTORY_PATH
change .authorize("user"); the text passed here to something else.
When you run your app again it should prompt you for authorization again.
I would comment but I have too few rep.
I guess you didn't properly set up the permissions of your access token. The error message suggessts, that you need a specific set of permissions to access the ressource
I'm trying to build a Compute client based on a key .JSON file. I'm looking at the examples found here but they are outdated and not working anymore.
I can't find any example in the current offical docs here.
Here is what I'm currently trying:
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.compute.Compute;
import com.google.api.services.compute.model.Instance;
import com.google.api.services.compute.model.InstanceList;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
public class Application {
private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
public static void main(String[] args) throws IOException, GeneralSecurityException {
InputStream credentialsJSON = Application.class.getClassLoader().getResourceAsStream("mykey.json");
JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
GoogleCredential cred = GoogleCredential.fromStream(credentialsJSON ,httpTransport,JSON_FACTORY);
// Create Compute Engine object for listing instances.
Compute compute = new Compute.Builder(httpTransport, JSON_FACTORY, cred.getRequestInitializer())
.setApplicationName("myapplication")
.build();
InstanceList instanceList = compute.instances().list("PROJECT_NAME", "europe-west3-a").execute();
for (Instance instance : instanceList.getItems()) {
System.out.println(instance.getId());
}
}
}
But it throws the following error:
Exception in thread "main" com.google.api.client.googleapis.json.GoogleJsonResponseException: 401 Unauthorized
{
"code" : 401,
"errors" : [ {
"domain" : "global",
"location" : "Authorization",
"locationType" : "header",
"message" : "Login Required.",
"reason" : "required"
} ],
"message" : "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.",
"status" : "UNAUTHENTICATED"
}
I don't understand because the file get's parsed correctly. Also the GoogleCredential model I'm using seems to be deprecated.
You need these two dependencies:
<dependencies>
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-compute</artifactId>
<version>v1-rev20200311-1.30.9</version>
</dependency>
<dependency>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>
<version>0.20.0</version>
</dependency>
</dependencies>
The google-auth-library-oauth2-http dependency repository can be found here. Switching to this new method worked for me.
And here's the working code:
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.compute.Compute;
import com.google.api.services.compute.ComputeScopes;
import com.google.auth.http.HttpCredentialsAdapter;
import com.google.auth.oauth2.GoogleCredentials;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
public class GCPComputeClientHelper {
private static Compute compute = null;
protected GCPComputeClientHelper() {
// Exists only to defeat instantiation
}
public static Compute getComputeInstance() throws GeneralSecurityException, IOException {
if (compute == null) {
compute = build();
}
return compute;
}
private static Compute build() throws GeneralSecurityException, IOException {
// Create http transporter needed for Compute client
HttpTransport HTTP_TRANSPORTER = GoogleNetHttpTransport.newTrustedTransport();
// Read GCP service account credentials JSON key file
InputStream serviceAccountJsonKey = GCPComputeClientHelper.class.getClassLoader().getResourceAsStream("mykeyfile.json");
// Authenticate based on the JSON key file
GoogleCredentials credentials = GoogleCredentials.fromStream(serviceAccountJsonKey);
credentials = credentials.createScoped(ComputeScopes.CLOUD_PLATFORM);
HttpRequestInitializer requestInitializer = new HttpCredentialsAdapter(credentials);
// Create and return GCP Compute client
return new Compute.Builder(HTTP_TRANSPORTER, JacksonFactory.getDefaultInstance(), requestInitializer)
.setApplicationName("myapplication")
.build();
}
}
I am trying to perform a simple push traces operation to my Google Cloud Trace project and I simply can't seem to send data across.
Here is my build.gradle file:
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
compile 'com.google.oauth-client:google-oauth-client-java6:1.20.0'
compile 'com.google.apis:google-api-services-cloudtrace:v1-rev6-1.22.0'
}
jar {
from configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
And the following Java code with dummy info for the project ID and my secrets file:
package test;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.cloudtrace.v1.CloudTrace;
import com.google.api.services.cloudtrace.v1.CloudTraceScopes;
import com.google.api.services.cloudtrace.v1.model.Trace;
import com.google.api.services.cloudtrace.v1.model.TraceSpan;
import com.google.api.services.cloudtrace.v1.model.Traces;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.time.Instant;
import java.util.Collections;
public class Test {
public static void main(String[] args) throws IOException, GeneralSecurityException {
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
GoogleCredential cred = GoogleCredential
.fromStream(
new FileInputStream("/path/to/secrets.json"),
httpTransport,
jsonFactory)
.createScoped(Collections.singletonList(CloudTraceScopes.TRACE_APPEND));
CloudTrace gceTrace = new CloudTrace.Builder(httpTransport, jsonFactory, cred)
.setApplicationName("Google Cloud Trace test app")
.build();
TraceSpan span1 = new TraceSpan();
span1.setName("test");
span1.setStartTime(Long.toString(Instant.now().toEpochMilli()*1000000)+"Z");
Trace trace = new Trace();
trace.setSpans(Collections.singletonList(span1));
Traces traces = new Traces();
traces.setTraces(Collections.singletonList(trace));
gceTrace.projects().patchTraces("myprojectid", traces).execute();
}
}
I currently get the following error that contains no helpful indication except something seems to be wrong with my startTime value:
Exception in thread "main" com.google.api.client.googleapis.json.GoogleJsonResponseException: 400 Bad Request
{
"code" : 400,
"errors" : [ {
"domain" : "global",
"message" : "Invalid value at 'traces.traces[0].spans[0].start_time' (type.googleapis.com/google.protobuf.Timestamp), Field 'startTime', Invalid time format: Failed to parse input",
"reason" : "badRequest"
} ],
"message" : "Invalid value at 'traces.traces[0].spans[0].start_time' (type.googleapis.com/google.protobuf.Timestamp), Field 'startTime', Invalid time format: Failed to parse input",
"status" : "INVALID_ARGUMENT"
}
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 test.Test.main(Test.java:44)
I have tried to replace the startTime with the following value:
span1.setStartTime("2016-08-04T01:00:00Z");
which gives me:
Exception in thread "main" com.google.api.client.googleapis.json.GoogleJsonResponseException: 400 Bad Request
{
"code" : 400,
"errors" : [ {
"domain" : "global",
"message" : "Request contains an invalid argument.",
"reason" : "badRequest"
} ],
"message" : "Request contains an invalid argument.",
"status" : "INVALID_ARGUMENT"
}
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 test.Test.main(Test.java:44)
I also tried adding a endTime with:
span1.setEndTime("2016-08-04T01:00:01Z");
which also gives me the same error.
I'm pretty much at a lost at what needs to be done as I cannot find a single working Java example for this.
Thank you in advance for any pointers for a working solution.
Finally figured it out. Here's a working example with mandatory and optional fields pointed out.
build.gradle
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
compile 'com.google.oauth-client:google-oauth-client-java6:1.20.0'
compile 'com.google.apis:google-api-services-cloudtrace:v1-rev6-1.22.0'
}
jar {
from configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
Test.java
package test;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.cloudtrace.v1.CloudTrace;
import com.google.api.services.cloudtrace.v1.CloudTraceScopes;
import com.google.api.services.cloudtrace.v1.model.Trace;
import com.google.api.services.cloudtrace.v1.model.TraceSpan;
import com.google.api.services.cloudtrace.v1.model.Traces;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class Test {
public static void main(String[] args) throws IOException, GeneralSecurityException {
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
GoogleCredential cred = GoogleCredential
.fromStream(
new FileInputStream("/path/to/secrets.json"),
httpTransport,
jsonFactory)
.createScoped(Collections.singletonList(CloudTraceScopes.TRACE_APPEND));
CloudTrace gceTrace = new CloudTrace.Builder(httpTransport, jsonFactory, cred)
.setApplicationName("Google Cloud Trace test app")
.build();
// They are optional
Map<String, String> labels = new HashMap<>();
labels.put("key1", "val1");
TraceSpan span = new TraceSpan();
span.setSpanId(new BigInteger("1")); // Mandatory
span.setName("test"); // Optional
span.setKind("RPC_SERVER"); // Optional
span.setStartTime("2016-08-04T01:00:00Z"); // Optional
span.setEndTime("2016-08-04T01:00:01Z"); // Optional
span.setLabels(labels); // Optional
Trace trace = new Trace();
trace.setProjectId("myprojectid"); // Mandatory
trace.setTraceId("A096D4956A424EEB98AE7863505B1E1F"); // Mandatory
trace.setSpans(Collections.singletonList(span)); // Mandatory
Traces traces = new Traces();
traces.setTraces(Collections.singletonList(trace)); // Mandatory
gceTrace.projects().patchTraces("myprojectid", traces).execute();
}
}
While some values are optional, like startTime or endTime, it makes sense to put something there.
I managed to put it together thanks to this question showing the expected values and looking at the REST API doc describing each field, especially for cryptic values like Trace ID:
patchTraces()
Trace and TraceSpan
This is my whole code. I want to give a video ID which from youtube to get the comments related to this vedio ID. But always show that I have Insufficient Permission.
I have tried to address this, and found youtube .commentThreads().list("snippet").setVideoId("tLTm_POao1c") .setTextFormat("plainText").execute(); is the problem. Specifically, execute() in this line calls "Insufficient Permission".
Why is this?
package com.google.api.services.samples.youtube.cmdline.youtube_cmdline_addsubscription_sample;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.List;
import com.google.api.client.util.Preconditions;//aiyamaya
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp;
import com.google.api.client.extensions.java6.auth.oauth2.FileCredentialStore;
import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
//import com.google.api.services.youtube.YouTube;
import com.google.api.services.youtube.model.ResourceId;
import com.google.api.services.youtube.model.Subscription;
import com.google.api.services.youtube.model.SubscriptionSnippet;
import com.google.common.collect.Lists;
import com.google.api.services.youtube.model.CommentThreadListResponse;
import com.google.api.services.youtube.model.CommentThread;
import com.google.api.services.youtube.model.CommentSnippet;
import com.google.api.services.youtube.YouTube;
import com.google.api.services.samples.youtube.cmdline.youtube_cmdline_addsubscription_sample.Auth;
//import com.google.api.services.samples.youtube.cmdline.Auth;
/**
* Demo of subscribing user to a channel using the YouTube Data API (V3) with
* OAuth2 for authorization.
*
* #author Ibrahim Ulukaya
*/
public class AddSubscription {
/** Global instance of the HTTP transport. */
// private static final HttpTransport HTTP_TRANSPORT = new
// NetHttpTransport();
/** Global instance of the JSON factory. */
// private static final JsonFactory JSON_FACTORY = new JacksonFactory();
/** Global instance of Youtube object to make all API requests. */
private static YouTube youtube;
public static void main(String[] args) {
System.setProperty("proxySet", "true");
System.setProperty("proxyHost", "bproxy.rmit.edu.au");
System.setProperty("proxyPort", "8080");
List<String> scopes = Lists
.newArrayList("https://www.googleapis.com/auth/youtube");
try {
// Authorization.
Credential credential = Auth.authorize(scopes, "commentthreads");
// This object is used to make YouTube Data API requests.
youtube = new YouTube.Builder(Auth.HTTP_TRANSPORT,
Auth.JSON_FACTORY, credential).setApplicationName(
"youtube-cmdline-commentthreads-sample").build();
// YouTube object used to make all API requests.
CommentThreadListResponse videoCommentsListResponse = youtube
.commentThreads().list("snippet").setVideoId("tLTm_POao1c")
.setTextFormat("plainText").execute();
List<CommentThread> videoComments = videoCommentsListResponse
.getItems();
} catch (GoogleJsonResponseException e) {
System.err.println("GoogleJsonResponseException code: "
+ e.getDetails().getCode() + " : "
+ e.getDetails().getMessage());
e.printStackTrace();
} catch (IOException e) {
System.err.println("IOException: " + e.getMessage());
e.printStackTrace();
} catch (Throwable t) {
System.err.println("Throwable: " + t.getMessage());
t.printStackTrace();
}
}
/*
* Returns a channel id (String) from user via the terminal.
*/
}
This is the error information:
May 22, 2015 8:19:15 AM com.google.api.client.util.store.FileDataStoreFactory setPermissionsToOwnerOnly
WARNING: unable to change permissions for everybody: C:\Users\E18300\.oauth-credentials
May 22, 2015 8:19:15 AM com.google.api.client.util.store.FileDataStoreFactory setPermissionsToOwnerOnly
WARNING: unable to change permissions for owner: C:\Users\E18300\.oauth-credentials
2015-05-22 08:19:15.192:INFO::Logging to STDERR via org.mortbay.log.StdErrLog
2015-05-22 08:19:15.193:INFO::jetty-6.1.26
2015-05-22 08:19:15.212:INFO::Started SocketConnector#localhost:8080
2015-05-22 08:19:20.433:INFO::Stopped SocketConnector#localhost:8080
GoogleJsonResponseException code: 403 : Insufficient Permission
com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden
{
"code" : 403,
"errors" : [ {
"domain" : "global",
"message" : "Insufficient Permission",
"reason" : "insufficientPermissions"
} ],
"message" : "Insufficient Permission"
}
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:321)
at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:1045)
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 com.google.api.services.samples.youtube.cmdline.youtube_cmdline_addsubscription_sample.AddSubscription.main(AddSubscription.java:86)
The documentation lacks in this part, but you need to use the scope https://www.googleapis.com/auth/youtube.force-ssl to retrieve comments from the YouTube API. Took me way too much time to solve this issue.
var OAUTH2_SCOPES = [
'https://www.googleapis.com/auth/youtube',
'https://www.googleapis.com/auth/youtube.force-ssl'
];
to retrieve comments from the YouTube API add
https://www.googleapis.com/auth/youtube.force-ssl
this into OAUTH2_SCOPES array
As the error message indicates, your request does not have sufficient permissions. If you look at the API Reference Overview you will see:
Every request must either specify an API key (with the key parameter) or provide an OAuth 2.0 token. Your API key is available in the API console's API Access pane for your project.
For example I am able to view the comment thread list for a video by making a GET request to this link in the browser directly:
https://www.googleapis.com/youtube/v3/commentThreads?part=snippet&key=YOUR_KEY&videoId=tLTm_POao1c. You will need to replace YOUR_KEY with your application key that you can find in your Google developer console.
I don't know why the code sample for comment threads does not mention anything about the need to include the API key (probably because it is assumed that you read the API Overview first). But
if you look at this other code sample, you will see that you can include a developer key in a local file that you can load into a Properties object:
// Read the developer key from the properties file.
Properties properties = new Properties();
try {
InputStream in = Search.class.getResourceAsStream("/" + PROPERTIES_FILENAME);
properties.load(in);
} catch (IOException e) {
System.err.println("There was an error reading " + PROPERTIES_FILENAME + ": " + e.getCause()
+ " : " + e.getMessage());
System.exit(1);
}
Further down the line, the api key is extracted from the Properties object and is used in the search API call:
// Set your developer key from the Google Developers Console for
// non-authenticated requests. See:
// https://console.developers.google.com/
String apiKey = properties.getProperty("youtube.apikey");
search.setKey(apiKey);
search.setQ(queryTerm);
In a similar manner, you can call setKey() on your code, as described by the JavaDocs: https://developers.google.com/resources/api-libraries/documentation/youtube/v3/java/latest/com/google/api/services/youtube/YouTube.CommentThreads.List.html#setKey(java.lang.String)
So, you may need to add something like this:
CommentThreadListResponse videoCommentsListResponse = youtube.commentThreads()
.list("snippet")
.setKey(YOUR_KEY)
.setVideoId("tLTm_POao1c")
.setTextFormat("plainText")
.execute();
You don't even need the Properties file, unless you plan to change the API key after you write the code and deploy it.
You only need a key to retrieve comments. Something like this:
String apiUrl = "https://www.googleapis.com/youtube/v3/commentThreads?part=snippet%2Creplies&videoId=" + ytId + "&moderationStatus=published&order=relevance&maxResults=5&key=" + apiKey;
Then handle the Json response.
I'm trying to access Google BigQuery using Service Account approach. My code is as follows:
private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
private static final JsonFactory JSON_FACTORY = new JacksonFactory();
GoogleCredential credentials = new GoogleCredential.Builder()
.setTransport(HTTP_TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId("XXXXX#developer.gserviceaccount.com")
.setServiceAccountScopes(BigqueryScopes.BIGQUERY)
.setServiceAccountPrivateKeyFromP12File(
new File("PATH-TO-privatekey.p12"))
.build();
Bigquery bigquery = Bigquery.builder(HTTP_TRANSPORT, JSON_FACTORY).setHttpRequestInitializer(credentials)
.build();
com.google.api.services.bigquery.Bigquery.Datasets.List datasetRequest = bigquery.datasets().list(
"PROJECT_ID");
DatasetList datasetList = datasetRequest.execute();
if (datasetList.getDatasets() != null) {
java.util.List<Datasets> datasets = datasetList.getDatasets();
System.out.println("Available datasets\n----------------");
for (Datasets dataset : datasets) {
System.out.format("%s\n", dataset.getDatasetReference().getDatasetId());
}
}
But it throws the following exception:
Exception in thread "main" com.google.api.client.googleapis.json.GoogleJsonResponseException: 401 Unauthorized
{
"code" : 401,
"errors" : [ {
"domain" : "global",
"location" : "Authorization",
"locationType" : "header",
"message" : "Authorization required",
"reason" : "required"
} ],
"message" : "Authorization required"
}
at com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:159)
at com.google.api.client.googleapis.json.GoogleJsonResponseException.execute(GoogleJsonResponseException.java:187)
at com.google.api.client.googleapis.services.GoogleClient.executeUnparsed(GoogleClient.java:115)
at com.google.api.client.http.json.JsonHttpRequest.executeUnparsed(JsonHttpRequest.java:112)
at com.google.api.services.bigquery.Bigquery$Datasets$List.execute(Bigquery.java:979)
The exception is fired on this line:
DatasetList datasetList = datasetRequest.execute();
I'm getting the account ID from Google's API console from the second line on the section that looks like this:
Client ID: XXXXX.apps.googleusercontent.com
Email address: XXXXX#developer.gserviceaccount.com
What am I missing?
Eureka! Both Eric's and Michael's code works well.
The error posted in the question can be reproduced by setting the time on the client machine incorrectly. Fortunately, it can be solved by setting the time on the client machine correctly.
Note: For what it's worth, I synchronized the time on a Windows 7 box using the "Update now" button in the "Internet Time Settings" dialog. I figured that should be pretty idiot-proof... but I guess I beat the system. It corrected the seconds but left the machine off by exactly one minute. The BigQuery call failed after that. It succeeded after I manually changed the time.
Our error handling code in the Java library needs to be improved a bit!
It looks like the signed JWT for requesting an OAuth access token is failing. You can see this by enabling the logs that #MichaelManoochehri mentioned above.
There's only a few things that I think could be causing this failure:
Invalid signature (using the wrong key)
Invalid e-mail address for the service account (I think that's been ruled out)
Invalid date/time stamp used for generating the signed blob (an issue date, and an expiration date)
Invalid scope (I think that's been ruled out)
You should check that your date/time is properly set on your server with the proper timezone -- sync'd to NTP. You can use time.gov to see the official US atomic clock time.
EDIT: The answer I gave below is relevant to using Google App Engine Service Accounts - leaving here for reference.
Double check that you have added your service account address to your project's team page as an owner.
I'd recommend using the AppIdentityCredential class to handle service account auth. Here's a small snippet that demonstrates this, and I'll add additional documentation about this on the BigQuery API developer page.
Also, make sure that you are using the latest version of the Google Java API client (as of today, it's version "v2-rev5-1.5.0-beta" here).
import java.io.IOException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.google.api.client.googleapis.extensions.appengine.auth.oauth2.AppIdentityCredential;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.http.json.JsonHttpRequest;
import com.google.api.client.http.json.JsonHttpRequestInitializer;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.api.services.bigquery.Bigquery;
import com.google.api.services.bigquery.BigqueryRequest;
#SuppressWarnings("serial")
public class Bigquery_service_accounts_demoServlet<TRANSPORT> extends HttpServlet {
// ENTER YOUR PROJECT ID HERE
private static final String PROJECT_ID = "";
private static final HttpTransport TRANSPORT = new NetHttpTransport();
private static final JsonFactory JSON_FACTORY = new JacksonFactory();
private static final String BIGQUERY_SCOPE = "https://www.googleapis.com/auth/bigquery";
AppIdentityCredential credential = new AppIdentityCredential(BIGQUERY_SCOPE);
Bigquery bigquery = Bigquery.builder(TRANSPORT,JSON_FACTORY)
.setHttpRequestInitializer(credential)
.setJsonHttpRequestInitializer(new JsonHttpRequestInitializer() {
public void initialize(JsonHttpRequest request) {
BigqueryRequest bigqueryRequest = (BigqueryRequest) request;
bigqueryRequest.setPrettyPrint(true);
}
}).build();
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
resp.setContentType("text/plain");
resp.getWriter().println(bigquery.datasets()
.list(PROJECT_ID)
.execute().toString());
}
}
Here is a complete snippet for reference:
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.services.bigquery.Bigquery;
import com.google.api.services.bigquery.Bigquery.Datasets;
import com.google.api.services.bigquery.model.DatasetList;
import java.io.File;
import java.io.IOException;
import java.security.GeneralSecurityException;
public class BigQueryJavaServiceAccount {
private static final String SCOPE = "https://www.googleapis.com/auth/bigquery";
private static final HttpTransport TRANSPORT = new NetHttpTransport();
private static final JsonFactory JSON_FACTORY = new JacksonFactory();
public static void main(String[] args) throws IOException, GeneralSecurityException {
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 bigquery = Bigquery.builder(TRANSPORT, JSON_FACTORY)
.setApplicationName("Google-BigQuery-App/1.0")
.setHttpRequestInitializer(credential).build();
Datasets.List datasetRequest = bigquery.datasets().list("publicdata");
DatasetList datasetList = datasetRequest.execute();
System.out.format("%s\n", datasetList.toPrettyString());
}