Google API - Required 'compute.globalOperations.get' permission - google-api

I'm trying to modify an instance's tags list using the goolge compute engine API for Java. My pom.xml imports this dependency:
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-compute</artifactId>
<version>v1-rev173-1.23.0</version>
</dependency>
I can execute the action that will update the tags associated with a VM successfully:
public boolean setInstanceTags(Compute computeConnection, ArrayList<String> nwTags, String projectId, String zone, String instanceName) {
Instance instance = computeConnection.instances().get(projectId, zone, instanceName).execute();
Tags tagsToSet = new Tags();
tagsToSet.setFingerprint(instance.getTags().getFingerprint());
tagsToSet.setItems(new ArrayList<String>());
for (String tag: nwTags) {
tagsToSet.getItems().add(tag);
}
Operation setTagsOperation = computeConnection.instances().setTags(projectId, zone, instanceName, tagsToSet).execute();
In order to get feedback on whether that operation succeeded I would like to pull the operation status as follows:
String setTagsOperationId = setTagsOperation.getName();
setTagsOperation = computeConnection.globalOperations().get(projectId, setTagsOperationId).execute();
This throws this error:
"code" : 403,
"errors" : [ {
"domain" : "global",
"message" : "Required 'compute.globalOperations.get' permission for 'projects/myproject/global/operations/operation-1523604756600-569b5e04b94c3-a87939f4-4e293939'",
"reason" : "forbidden"
} ],
"message" : "Required 'compute.globalOperations.get' permission for 'projects/myproject/global/operations/operation-1523604756600-569b5e04b94c3-a87939f4-4e293939'"
But the service account I'm using does have the "Compute Admin" IAM role and my code is also setting the admin scope:
SCOPES = Arrays.asList(ComputeScopes.COMPUTE);
I'm using the same account/permissions to create firewall rules and pull the status on those operations successfully. Not sure why there is a difference in permissions for pulling operation status for instances.setTags operations and firewalls.insert.
The only hint I found is when pulling data on the firewalls.insert operation the 'selfLink' shows that the operation is located in the global scope:
"https://www.googleapis.com/compute/v1/projects/myproject/global/operations/operation-1523604247193-569b5c1eea5a8-2ccf40e9-8815af38"
where as the instances.setTags operation selfLink shows that this operation is located in a specific zone:
"https://www.googleapis.com/compute/v1/projects/myproject/zones/us-central1-c/operations/operation-1523604346365-569b5c7d7e449-dc64de03-fdb77847"

You can modify the tag of an instance from console and check that this operation is a zonal operation, not a global operation. Below selfLink can be found from Equivalent REST response from any modify tag operation.
"selfLink": "https://www.googleapis.com/compute/v1/projects/[project_name]/zones/us-central1-f/operations/operation-0000000000000-0000000000000-00000000-00000000",
I believe for this reason, it is needed to use a zonal method rather than global method. You can use the below method to resolve this issue.
Method: zoneOperations.get
That said, Compute Admin role already has all required permissions including compute.zoneOperations permission to work in this case.

I have tested the following API [1] call with a service account with role "Compute Admin":
$ curl -H"Authorization: Bearer "$(gcloud auth print-access-token) https://www.googleapis.com/compute/v1/projects/[PROJECT_ID]/global/operations/[OPERATION_NAME]
And it returned me the expected value with no error.
In order to know if your code and service account have the right role, you can try the following code extracted from the official documentation [2] with the value set to "compute.globalOperations.get".
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.cloudresourcemanager.CloudResourceManager;
import com.google.api.services.cloudresourcemanager.model.TestIamPermissionsRequest;
import com.google.api.services.cloudresourcemanager.model.TestIamPermissionsResponse;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Arrays;
public class CloudResourceManagerExample {
public static void main(String args[]) throws IOException, GeneralSecurityException {
// REQUIRED: The resource for which the policy detail is being requested.
// See the operation documentation for the appropriate value for this field.
String resource = "my-resource"; // TODO: Update placeholder value.
// TODO: Assign values to desired fields of `requestBody`:
TestIamPermissionsRequest requestBody = new TestIamPermissionsRequest();
CloudResourceManager cloudResourceManagerService = createCloudResourceManagerService();
CloudResourceManager.Projects.TestIamPermissions request =
cloudResourceManagerService.projects().testIamPermissions(resource, requestBody);
TestIamPermissionsResponse response = request.execute();
// TODO: Change code below to process the `response` object:
System.out.println(response);
}
public static CloudResourceManager createCloudResourceManagerService()
throws IOException, GeneralSecurityException {
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
GoogleCredential credential = GoogleCredential.getApplicationDefault();
if (credential.createScopedRequired()) {
credential =
credential.createScoped(Arrays.asList("https://www.googleapis.com/auth/cloud-platform"));
}
return new CloudResourceManager.Builder(httpTransport, jsonFactory, credential)
.setApplicationName("Google-CloudResourceManagerSample/0.1")
.build();
}
}
This way you will know if your code is using the correct service account or if there is an issue.
[1] https://cloud.google.com/compute/docs/reference/rest/beta/globalOperations/get
[2] https://cloud.google.com/resource-manager/reference/rest/v1/projects/testIamPermissions

Related

Dialogflow CX | How to close/reset a conversation

How can I close or reset a conversation programmatically from Java app?. According to Dialogflow CX documentation "A session remains active and its data is stored for 30 minutes after the last request is sent for the session."
I want to keep the session active for less time. For example, if I want the session to be active for 5 minutes, when user sends a message 5 minutes or more after last message, conversation must start again and previous flows must be closed and context parameters must be deleted.
With Dialogflow ES it is posible using ContextsClient, however new version does not offer ContextsClient class.
Dialogflow CX uses State Handlers to control conversation paths, unlike Dialogflow ES which uses Contexts.
For Dialogflow CX, you can end the current session by using the END_SESSION symbolic transition target. Once the END_SESSION transition target is invoked, it clears the current session and the next user input will restart the session at the start page of the Default Start Flow.
To achieve your desired use case, you’ll have to create your own implementation for it. Note that the solution below will only work if you integrate your Dialogflow CX agent to a custom front-end.
First, you should add an Event Handler to all of your Pages - so that the Event Handler will be accessible in any part of the conversation flow. In this Event Handler, define a custom event - for example: clearsession. Then, set its Transition to End Session Page. Once the clearsession event is invoked, it will end the current session.
Then, using your own business logic, you can create a custom function that could act as a timer for each user query. Once the timer reaches 5 minutes, your custom application should send a detectIntent request to your CX agent programmatically. This detectIntent request must contain the current session ID and the custom event (from the previously created Event Handler).
Here’s a sample detectIntent request that invokes a custom event using the Java Client Library:
// [START dialogflow_cx_detect_intent_event]
import com.google.api.gax.rpc.ApiException;
import com.google.cloud.dialogflow.cx.v3.*;
import com.google.common.collect.Maps;
import java.io.IOException;
import java.util.List;
import java.util.Map;
public class DetectIntent {
// DialogFlow API Detect Intent sample with event input.
public static Map<String, QueryResult> detectIntentEvent(
String projectId,
String locationId,
String agentId,
String sessionId,
String languageCode,
String event)
throws IOException, ApiException {
SessionsSettings.Builder sessionsSettingsBuilder = SessionsSettings.newBuilder();
if (locationId.equals("global")) {
sessionsSettingsBuilder.setEndpoint("dialogflow.googleapis.com:443");
} else {
sessionsSettingsBuilder.setEndpoint(locationId + "-dialogflow.googleapis.com:443");
}
SessionsSettings sessionsSettings = sessionsSettingsBuilder.build();
Map<String, QueryResult> queryResults = Maps.newHashMap();
// Instantiates a client
try (SessionsClient sessionsClient = SessionsClient.create(sessionsSettings)) {
// Set the session name using the projectID (my-project-id), locationID (global), agentID
// (UUID), and sessionId (UUID).
SessionName session = SessionName.of(projectId, locationId, agentId, sessionId);
System.out.println("Session Path: " + session.toString());
EventInput.Builder eventInput = EventInput.newBuilder().setEvent(event);
// Build the query with the EventInput and language code (en-US).
QueryInput queryInput =
QueryInput.newBuilder().setEvent(eventInput).setLanguageCode(languageCode).build();
// Build the DetectIntentRequest with the SessionName and QueryInput.
DetectIntentRequest request =
DetectIntentRequest.newBuilder()
.setSession(session.toString())
.setQueryInput(queryInput)
.build();
// Performs the detect intent request.
DetectIntentResponse response = sessionsClient.detectIntent(request);
// Display the query result.
QueryResult queryResult = response.getQueryResult();
System.out.println("====================");
System.out.format(
"Detected Intent: %s (confidence: %f)\n",
queryResult.getIntent().getDisplayName(), queryResult.getIntentDetectionConfidence());
}
return queryResults;
}
public static void main(String[] args) {
String projectId = "<project-id>";
String locationId = "<location-id>";
String agentId = "<agent-id>";
String sessionId = "<current-session-id>";
String languageCode = "<language-code>";
String event = "clearsession";
try{
detectIntentEvent(projectId,locationId,agentId,sessionId, languageCode, event);
} catch (IOException e){
System.out.println(e.getMessage());
}
}
}
// [END dialogflow_cx_detect_intent_event]

401 Access denied. You are not authorized to read activity records

I'm trying to get data from Reports API.
I get access token for service account and using it in GET request. Response always
{
"error": {
"errors": [
{
"domain": "global",
"reason": "authError",
"message": "Access denied. You are not authorized to read activity records.",
"locationType": "header",
"location": "Authorization"
}
],
"code": 401,
"message": "Access denied. You are not authorized to read activity records."
}
}
I'm using Java for request. Without Google API library (client requirement). Source code is
String urlString = "https://www.googleapis.com/admin/reports/v1/activity/users/all/applications/drive?maxResults=25";
URL url = new URL(urlString);
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
// optional default is GET
urlConnection.setRequestMethod("GET");
urlConnection.setDoInput(true);
// Add request header.
urlConnection.setRequestProperty("Authorization", "Bearer " + accessToken.getValue());
int responseCode = urlConnection.getResponseCode();
System.out.println("\nSending 'GET' request to URL : " + urlString);
System.out.println("Response Code : " + responseCode);
BufferedReader bufferedReader;
if (responseCode == 200) {
bufferedReader = new BufferedReader(
new InputStreamReader(urlConnection.getInputStream()));
} else {
bufferedReader = new BufferedReader(
new InputStreamReader(urlConnection.getErrorStream()));
}
String inputLine;
StringBuffer stringBuffer = new StringBuffer();
while ((inputLine = bufferedReader.readLine()) != null) {
stringBuffer.append(inputLine);
}
bufferedReader.close();
System.out.println(stringBuffer.toString());
Can you, please, help me what I'm missing?
Regards,
Aleks.
"Access denied. You are not authorized to read activity records.",
means just that the user you are authecated with does not have access to do what you are trying to do. To use a service account with this api you need to set up domain wide delegation
In enterprise applications you may want to programmatically access a user's data without any manual authorization on their part. In G Suite domains, the domain administrator can grant third-party applications with domain-wide access to its users' data — this is referred as domain-wide delegation of authority. To delegate authority this way, domain administrators can use service accounts with OAuth 2.0.
Go to your G Suite domain’s Admin console.
Select Security from the list of controls. If you don't see Security listed, select More controls from the gray bar at the bottom of the page, then select Security from the list of controls.
Select Advanced settings from the list of options.
Select Manage third party OAuth Client access in the Authentication section.
In the Client name field enter the service account's Client ID.
In the One or More API Scopes field enter the list of scopes that your application should be granted access to (see image below). For example if you need domain-wide access to activity reports enter: https://www.googleapis.com/auth/admin.reports.audit.readonly
Click the Authorize button.
Just the below code. Two things are very important here: email-id i.e. SERVICE_ACCOUNT_EMAIL and json file SERVICE_ACCOUNT_PKCS12_FILE_PATH:
Source: https://developers.google.com/admin-sdk/reports/v1/guides/delegation
I am using the the GO version of it and it works like a charm after spending 2 days on it :)) (by the way GO version can be found here: https://developers.google.com/admin-sdk/directory/v1/guides/delegation#go)
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
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.services.admin.reports.Reports;
import com.google.api.services.admin.reports.ReportsScopes;
...
/** Email of the Service Account */
private static final String SERVICE_ACCOUNT_EMAIL = "<some-id>#developer.gserviceaccount.com";
/** Path to the Service Account's Private Key file */
private static final String SERVICE_ACCOUNT_PKCS12_FILE_PATH = "/path/to/<public_key_fingerprint>-privatekey.p12";
/**
* Build and returns a Reports service object authorized with the service accounts
* that act on behalf of the given user.
*
* #param userEmail The email of the user. Needs permissions to access the Admin APIs.
* #return Reports service object that is ready to make requests.
*/
public static Reports getReportsService(String userEmail) throws GeneralSecurityException,
IOException, URISyntaxException {
HttpTransport httpTransport = new NetHttpTransport();
JacksonFactory jsonFactory = new JacksonFactory();
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
.setServiceAccountScopes(ReportsScopes.ADMIN_REPORTS_AUDIT_READONLY)
.setServiceAccountUser(userEmail)
.setServiceAccountPrivateKeyFromP12File(
new java.io.File(SERVICE_ACCOUNT_PKCS12_FILE_PATH))
.build();
Reports service = new Reports.Builder(httpTransport, jsonFactory, null)
.setHttpRequestInitializer(credential).build();
return service;
}

Login Required 401 using Google ServiceAccountCredential using Google Admin Directory API

I have tried to follow the simple example listed here: https://developers.google.com/admin-sdk/directory/v1/quickstart/dotnet
The difference is I generated a Service Account Credential, and assigned it as a Delegate with the Role Project Owner, so it has full access. I also assigned it the proper namespaces for scopes.
Here it has access to orgunits which is what I'm trying to list in the Directory API
Here is my service account defined
Here are my credentials
I downloaded the JSON for the credential and added it to my project. I can confirm that the code loades the ServiceAccountCredential and successfully authenticates and gets an access token by inspecting the debugger.
But then I pass the credential to the Service Initializer, and when I create and execute a request it fails with
{"Google.Apis.Requests.RequestError\r\nLogin Required [401]\r\nErrors [\r\n\tMessage[Login Required] Location[Authorization - header] Reason[required] Domain[global]\r\n]\r\n"}
Here's the code:
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using System;
using System.Collections.Generic;
using System.IO;
namespace DirectoryQuickstart
{
class Program
{
static string[] Scopes = { DirectoryService.Scope.AdminDirectoryUser, DirectoryService.Scope.AdminDirectoryOrgunit };
static string ApplicationName = "slea-crm";
static string Secret = "gsuite-secret.json";
static void Main(string[] args)
{
ServiceAccountCredential sac = GoogleCredential.FromFile(Secret).CreateScoped(Scopes).UnderlyingCredential as ServiceAccountCredential;
var token = sac.GetAccessTokenForRequestAsync().Result;
// Create Directory API service.
var service = new DirectoryService(new BaseClientService.Initializer()
{
HttpClientInitializer = sac,
ApplicationName = ApplicationName,
});
OrgunitsResource.ListRequest request = service.Orgunits.List(customerId: "REDACTED");
IList<OrgUnit> orgUnits = request.Execute().OrganizationUnits;
if (orgUnits != null && orgUnits.Count > 0)
{
foreach (var orgUnit in orgUnits)
{
Console.WriteLine("{0} ({1})", orgUnit.Name, orgUnit.OrgUnitPath);
}
}
else
{
Console.WriteLine("No orgunits found.");
}
Console.Read();
}
}
}
Here is the content of my JSON secret (with redactions)
What am I missing here?
EDIT: OK, I breakpoint the code while it generates the request, and I can see that no where does it set the Authorization token bearer in the headers. Why? I would expect this HttpClientInitializer class to take care of that, since the API docs say it knows how to handle that, and every example on the internet I've found shows it just passing the credential into the service initializer. But when I walked through it, even though the credential has already been granted an access token and one exists within it, nowhere does the request have the header updated.
The only thing I can see is there is some way to add an HTTP request interceptor where possibly I could do this myself, but wow, this seems really...bizarre -- after all this work they did on the dotnet client SDK, I honestly could have just written direct to the HTTP API and it would have been a lot simpler and easier to follow.
The missing piece of the puzzle is this line:
ServiceAccountCredential sac = GoogleCredential.FromFile(Secret)
.CreateScoped(Scopes)
.UnderlyingCredential as ServiceAccountCredential;
Needs to be modified to this:
static string userName = "admin#yourdomain.com" // valid user in your org
ServiceAccountCredential sac = GoogleCredential.FromFile(Secret)
.CreateScoped(Scopes)
.CreateWithUser(userName)
.UnderlyingCredential as ServiceAccountCredential;
Java/Python/Go sample of doing similar is here: https://developers.google.com/admin-sdk/directory/v1/guides/delegation#create_the_service_account_and_its_credentials
This has been answered but adding more details here. If anyone wants to impersonate user to upload file on google drive using Service account. Follow these steps
Create Service Account
Enable Site Wide delegation for service account
Get Service account client ID
Enable Client ID to use Google Drive API using Google Admin Console->Manage API
Use the below C# code to upload file
public static DriveService GetService()
{
string[] scopes = new string[] { DriveService.Scope.Drive };
//"SERVICE_ACCOUNT_EMAIL_HERE";
String serviceAccountEmail = "test-417#elated-graph-261115.iam.gserviceaccount.com";
// Scope and user email id which you want to impersonate
var initializer = new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = scopes,
User = "yourEmail#domain.com"
};
//get private key, from .JSON file
var credential = new ServiceAccountCredential(initializer.FromPrivateKey("-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCkHeAicu6uFQn0\n7KUVTjgZ68nQui8+c8NmKW8aW8vhkBIKfdewXFECiUlTMPyI+HXbubsCK5Dl2xBS\nnphLq6YyE0xEQxNFLYHwfUKuzGQ2rV+qObcZ0mLZjCaf+pw3YiRVuU6OtslLJKJH\n-----END PRIVATE KEY-----\n"));
// Create the service.
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "DriveAPI",
});
service.HttpClient.Timeout = TimeSpan.FromMinutes(100);
return service;
}
That's it, we are done above Code is using Impersonation/Delegation for uploading file on Google Drive using Service account
Reference : Upload file to Google Drive using Service Account in C# MVC (With Impersonation)

AWS Cognito JWT token validation

I am trying to enforce security based on AWS Cognito JWT token, the source is at https://github.com/IxorTalk/ixortalk.aws.cognito.jwt.security.filter.
But I have concern, which is per documentation http://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html#amazon-cognito-identity-user-pools-using-id-and-access-tokens-in-web-api
It says "The ID token expires one hour after the user authenticates. You should not process the ID token in your client or web API after it has expired."
Which is the error I am seeing in my logs,
com.nimbusds.jwt.proc.BadJWTException: Expired JWT. I assume, that the JWT token has already expired, what would be my steps to successfully enforce token based authorization?
If you are using Java, The way I have programmed is this,
// Parse the Cognito Keys and get the key by kid
// Key is just a class that is used for parsing JSON to POJO
Key key = this.keyService.getKeyByKeyId(JWT.decode(token).getKeyId());
// Use Key's N and E
BigInteger modulus = new BigInteger(1, Base64.decodeBase64(key.getN()));
BigInteger exponent = new BigInteger(1, Base64.decodeBase64(key.getE()));
// Create a publick key
PublicKey publicKey = null;
try {
publicKey = KeyFactory.getInstance("RSA").generatePublic(new RSAPublicKeySpec(modulus, exponent));
} catch (InvalidKeySpecException e) {
// Throw error
} catch (NoSuchAlgorithmException e) {
// Throw error
}
// get an algorithm instance
Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) publicKey, null);
// I verify ISS field of the token to make sure it's from the Cognito source
String iss = String.format("https://cognito-idp.%s.amazonaws.com/%s", REGION, POOL_ID);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer(iss)
.withClaim("token_use", "id") // make sure you're verifying id token
.build();
// Verify the token
DecodedJWT jwt = verifier.verify(token);
// Parse various fields
String username = jwt.getClaim("sub").asString();
String email = jwt.getClaim("email").asString();
String phone = jwt.getClaim("phone_number").asString();
String[] groups = jwt.getClaim("cognito:groups").asArray(String.class);
I am using this repo to verify and parsing of the tokens,
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.1</version>
</dependency>
Make sure you are importing the following,
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import org.apache.commons.codec.binary.Base64;
If the token is expired, it won't be varified.
You need the refresh token, that helps you get new identity and access tokens. The Cognito JS SDK refreshes the token automatically.
Now in your case, seems like you need to call the RefreshToken and add a check to see if the token is expired.
The identity/access tokens come with a expiration time so this is something you can do locally in your application before you use them.

Google Directory API returns Not Authorized when call users().list().execute()

I need to read the list of users (and groups) from my google domain.
So I went to my Google APIs Console and enabled Admin SDK and created a Service Account to call Google APIs. I use the following google libraries
google-api-services-admin-directory_v1-rev11-1.16.0-rc.jar
google-api-client-1.16.0-rc.jar
My code is
/*
* Global instance of the JSON factory.
*/
final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
/*
Global instance of the HTTP transport.
*/
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
Collection<String> scopeList = new ArrayList<>();
scopeList.add(DirectoryScopes.ADMIN_DIRECTORY_USER);
scopeList.add(DirectoryScopes.ADMIN_DIRECTORY_GROUP);
scopeList.add(DirectoryScopes.ADMIN_DIRECTORY_GROUP_MEMBER);
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId("nnnnnn#developer.gserviceaccount.com")
.setServiceAccountScopes(scopeList)
.setServiceAccountPrivateKeyFromP12File(new File("/Path/To/KeyFile/nnnnn-privatekey.p12"))
// .setServiceAccountUser("admin#mydomain.org")
.build();
Directory admin = new Directory.Builder(httpTransport, JSON_FACTORY, credential)
.setApplicationName("Test")
.setHttpRequestInitializer(credential).build();
Users users = admin.users().list().setDomain("mydomain.org").execute();
And I receive this on the last line
Error
{
"code" : 403,
"errors" : [ {
"domain" : "global",
"message" : "Not Authorized to access this resource/api",
"reason" : "forbidden"
} ],
"message" : "Not Authorized to access this resource/api"
}
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:1045)
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)
If I uncomment the commented line (.setServiceAccountUser("admin#mydomain.org")) then I get a different error
Exception in thread "main" com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad Request
{
"error" : "access_denied"
}
at com.google.api.client.auth.oauth2.TokenResponseException.from(TokenResponseException.java:105)
at com.google.api.client.auth.oauth2.TokenRequest.executeUnparsed(TokenRequest.java:287)
at com.google.api.client.auth.oauth2.TokenRequest.execute(TokenRequest.java:307)
at com.google.api.client.googleapis.auth.oauth2.GoogleCredential.executeRefreshToken(GoogleCredential.java:269)
at com.google.api.client.auth.oauth2.Credential.refreshToken(Credential.java:489)
at com.google.api.client.auth.oauth2.Credential.intercept(Credential.java:217)
at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:858)
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)
My account (admin#mydomain.org) is a super administrator. I suspect the Service Account needs to be granted access for the users API scope. But I can not find where I can grant this access. I have a classic UI mode of my Google CPanel. And I don't have "Manage client API access" page in the Advanced tools.
Also I'm not sure what I should use as an Application Name at .setApplicationName("Test").
Thanks for any help
you can go to "security" settings in the admin console (admin.google.com/AdminHome?chromeless=1&pli=1#SecuritySettings:); then click on advance settings > Manage third party OAuth Client access. After this map your client id(generated from appconsole code.google.com/apis/console under API access for oath2) and "One or More API Scopes". Use comma separated scopes as mentioned there. For google directory you can use https://www.googleapis.com/auth/admin.directory.group,https://www.googleapis.com/auth/admin.directory.user
Hope after this it works :)
I had the exact same problem, and was stucked on this sample
What helped me :
1/ I did not Delegate domain-wide authority to your service account, as suggested by Jay Lee. But after that, I still had the problem.
2/ Then, according to this post, the call to setServiceAccountUser(yourAdminAccount#yourDomain.com) is mandatory.
Google Directory API works with Compute Engine default service account, you do not need to setup Google Drive domain-wide. The only thing: you have to set serviceAccountUser, which is not supported in JSON based credentials. So you can
use P12 keys
user JSON credentials with workaround:
make credential copy:
import static com.google.api.client.googleapis.util.Utils.getDefaultJsonFactory;
import static com.google.api.client.googleapis.util.Utils.getDefaultTransport;
private static final String APPLICATION_NAME = "AnyAppName";
private final List<String> SCOPES = ImmutableList.of(
DirectoryScopes.ADMIN_DIRECTORY_GROUP_MEMBER, DirectoryScopes.ADMIN_DIRECTORY_USER, DirectoryScopes.ADMIN_DIRECTORY_GROUP);
private Directory service;
#PostConstruct
void init() throws GeneralSecurityException, IOException {
GoogleCredential credential;
try (InputStream is = new FileInputStream("./config/client_secret.json")) {
credential = GoogleCredential.fromStream(is);
}
GoogleCredential credentialWithUser = new GoogleCredential.Builder()
.setTransport(getDefaultTransport())
.setJsonFactory(getDefaultJsonFactory())
.setServiceAccountUser("admin#yourdomain.ru") // <--- mail of domain's admin
.setServiceAccountId(credential.getServiceAccountId())
.setServiceAccountScopes(SCOPES)
.setServiceAccountPrivateKey(credential.getServiceAccountPrivateKey())
.setServiceAccountPrivateKeyId(credential.getServiceAccountPrivateKeyId())
.setTokenServerEncodedUrl(credential.getTokenServerEncodedUrl()).build();
service = new Directory.Builder(getDefaultTransport(), getDefaultJsonFactory(), credentialWithUser).setApplicationName(APPLICATION_NAME).build();
}
public void members() throws IOException {
Members members = service.members().list("groupName#yourdomain.ru").execute();
System.out.println(members);
}
For my trial G Suite account it works!
You can grant the service account access to certain scopes in the Control Panel as explained in the Google Drive domain-wide documentation. Just use Admin SDK scopes instead.
The application name is used in the User-Agent header of requests and so is not overly important, just use your apps name and maybe version.

Resources