I'm trying to fetch a user from Google Directory API with the following request:
Collection<String> SCOPES = Arrays.asList("https://www.googleapis.com/auth/admin.directory.user.readonly", "https://www.googleapis.com/auth/admin.directory.user");
URL url = getClass().getResource("privatekey.p12");
GoogleCredential credential = new GoogleCredential.Builder()
.setJsonFactory(request.getJsonFactory())
.setServiceAccountId("foobar.com")
.setServiceAccountScopes(SCOPES)
.setTransport(new NetHttpTransport())
.setServiceAccountUser("foo#bar.com")
.setServiceAccountPrivateKeyFromP12File(new File(url.getPath())).build();
Directory dir = new Directory.Builder(GoogleNetHttpTransport.newTrustedTransport(), request.getJsonFactory(), credential)
.setApplicationName(request.getApplicationName())
.build();
But I'm getting the following error from the API:
Caused by: com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad Request
{
"error" : "access_denied",
"error_description" : "Requested scopes not allowed: https://www.googleapis.com/auth/admin.directory.user https://www.googleapis.com/auth/admin.directory.user.readonly"
}
I've checked the scopes are correct, anyone know what's the problem here?
Try using one scope or the other. If you only need read access, use the readonly scope. If you need read/write access to users, use the other.
Also, ensure that you've granted the service account's client_id access to these scopes in the Admin's control panel (admin.google.com) and that your ServiceAccountUser is a super admin in the domain.
Argh! I removed the old service account and created a new one. After that things started to roll. Guessing I had a wrong p12 file etc. Can finally breathe again, phew.
Related
I am new using Azure Graph Rest API Java using this repo.
My aim is to list all of the users in the AAD tenant
So far I was only able to get to this:
List<String> scopes= Arrays.asList("https://graph.microsoft.com/User.Read.All");
AzureProfile profile = new AzureProfile(tenantId, subscriptionId, AzureEnvironment.AZURE);
final ClientSecretCredential credential = new ClientSecretCredentialBuilder()
.clientId(clientId)
.clientSecret(clientSecret)
.tenantId(tenantId)
//.httpClient(client)
.authorityHost(profile.getEnvironment().getActiveDirectoryEndpoint())
.build();
TokenCredentialAuthProvider tokenCredentialAuthProvider = new TokenCredentialAuthProvider(scopes, credential);
GraphServiceClient<Request> graphClient =
GraphServiceClient
.builder()
.authenticationProvider(tokenCredentialAuthProvider)
.buildClient();
UserCollectionPage users = graphClient.users()
.buildRequest()
.get();
for(User user: users.getCurrentPage()){
System.out.println(user.displayName);
System.out.println(user.id);
System.out.println(user.userPrincipalName);
}
However, I run into this error instead:
Caused by: java.io.IOException:
java.util.concurrent.ExecutionException:
com.microsoft.aad.msal4j.MsalServiceException:
AADSTS1002012: The
provided value for scope https://graph.microsoft.com/User.Read.All
openid profile offline_access is not valid. Client credential flows
must have a scope value with /.default suffixed to the resource
identifier (application ID URI).
It seems the Scope that I have used is wrong/insufficient, but I am not too sure what should I use the scope with. Any idea?
It is written in the documentation that:
Client credentials requests in your client service must include
scope={resource}/.default. Here, {resource} is the web API that your
app intends to call, and wishes to obtain an access token for. Issuing
a client credentials request by using individual application
permissions (roles) is not supported. All the app roles (application
permissions) that have been granted for that web API are included in
the returned access token.
The Client Credential flow is best suited for situations where you have a Deamon App that will have to authenticate and get access to some kind of a resource through a Non-Interactive way, which in sequence means that the permissions for this Deamon App have been configured and consented from a step done prior to the auth request.
The /.default scope can be translated as the request of the Background App that runs unattended, to get the bulk of the permissions that it has been configured with and access the resource that it asks.
In plain english, the use of the above scope in the Client Credentials flow is a convention that has to be implemented always when this flow is chosen :P.
I tried to reproduce the same in my environment via Postman and got below results:
I registered one Azure AD application and added API permissions like below:
When I tried to generate access token with same scope as you via Postman using client credentials flow, I got same error as below:
POST https://login.microsoftonline.com/<tenantID>/oauth2/v2.0/token
grant_type:client_credentials
client_id: <appID>
client_secret: <secret_value>
scope: https://graph.microsoft.com/User.Read.All openid profile offline_access
Response:
To resolve the above error, you must change your scope to https://graph.microsoft.com/.default if you are using client credentials flow.
After changing the scope, I'm able to generate access token successfully like below:
POST https://login.microsoftonline.com/<tenantID>/oauth2/v2.0/token
grant_type:client_credentials
client_id: <appID>
client_secret: <secret_value>
scope: https://graph.microsoft.com/.default
Response:
When I used the above token to call below Graph query, I got the list of users with display name, id and user principal name successfully like below:
GET https://graph.microsoft.com/v1.0/users?$select=displayName,id,userPrincipalName
Response:
In your case, change scope value in your code like below:
List<String> scopes= Arrays.asList("https://graph.microsoft.com/.default");
Hi I'm trying to implement sending/receiving email using Google's gmail api on my server:
private GoogleCredential authorize(HttpTransport httpTransport, JsonFactory jsonFactory ) {
try{
Resource resource = new ClassPathResource("my_key_in_json_format.");
InputStream input = resource.getInputStream();
GoogleCredential credential = GoogleCredential.fromStream(input);
credential.createScoped(GmailScopes.all());
credential.refreshToken();
return credential;
}catch(IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
But I'm getting the following exception when the credential tries to refresh token:
com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad Request
{
"error" : "invalid_scope",
"error_description" : "Bad Request"
}
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:394)
at com.google.api.client.auth.oauth2.Credential.refreshToken(Credential.java:493)
at com.snobo.util.GmailService.authorize(GmailService.java:79)
I've tried changing the scope parameters to:
Collection<String> SCOPES = Collections.unmodifiableCollection(Arrays.asList(new String[]{GmailScopes.GMAIL_READONLY}));
And it also failed the same when refreshing token. Google's online document is not really Java friendly. Anyone run into similar issues?
I found the answer to my problem based on this thread after searching around:
400 Bad Request on Gmail API with php
"You should not be using a service account if you just want to access one account (your own). Service accounts are their own account and they're not Gmail accounts. They work well for APIs that don't need a user (e.g. maps, search) or when you are using a Google Apps for Work domain and want delegation enabled for all users in the domain (by domain admin, so you don't need individual user authorization)."
I have modified my implementation to use oauth web flow now. I'm really disappointed on Google's documentation as this matter should be addressed outright and as concise as possible. I'm sure "Service Account" and "domain wide delegation" mis-led many developers to use the Service Account approach for many types of personal/individual account application.
I'm trying to make requests to Drive API impersonating an user using a service account but I'm getting a TokenResponseException: 401 Unauthorized exception.
I've followed what is described in https://developers.google.com/identity/protocols/OAuth2ServiceAccount:
In https://console.developers.google.com, created a Project > Credentials > Service account > P12 file;
Enabled Drive API;
Enabled domain-wide delegation;
In https://console.developers.google.com/iam-admin/iam, added Project > Editor permission to email test#gmail.com (example).
Then, in my code:
File p12 = new File("p12FileFromDevelopersConsole.p12");
HttpTransport transport = GoogleNetHttpTransport.newTrustedTransport();
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
GoogleCredential c = new GoogleCredential.Builder()
.setTransport(transport)
.setJsonFactory(jsonFactory)
.setServiceAccountId("MY_ID#MY-ID.iam.gserviceaccount.com")
.setServiceAccountUser("test#gmail.com")
.setServiceAccountScopes(Arrays.asList(DriveScopes.DRIVE, DriveScopes.DRIVE_APPDATA, DriveScopes.DRIVE_FILE, DriveScopes.DRIVE_METADATA_READONLY))
.setServiceAccountPrivateKeyFromP12File(p12)
.build();
List<com.google.api.services.drive.model.File> files = drive.files()
.list()
.setSpaces("drive")
.setFields("nextPageToken, files(id, name, webViewLink)")
.setPageSize(10)
.execute() // < --- exception is thrown here
.getFiles();
for(com.google.api.services.drive.model.File f : files) {
System.out.println(f.getName());
}
The stack trace is:
com.google.api.client.auth.oauth2.TokenResponseException: 401 Unauthorized
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:384)
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:868)
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)
I could note that if I comment out the line .setServiceAccountUser("test#gmail.com"), I am able to execute code and it prints "Getting started" in my System.out.println.
I've seen many posts with this problem but none with a concrete solution. Then, I looked at documentation referenced above and it says many times about G Suite and then I realize it could be that these requests only work with a G Suite account. Am I right? If not, how can I make it work?
Yes, domain-wide delegation for service accounts is only for GSuite users because GSuite admins must grant service accounts domain-wide authority -- see Step 4 here: https://developers.google.com/identity/protocols/OAuth2ServiceAccount#delegatingauthority
Consider using regular OAuth (https://developers.google.com/identity/protocols/OAuth2WebServer) to grant access to your web app from your gmail account.
I am using the service account model and Google's Java API to retrieve and modify users.
I am able to successfully create a GoogleCredential object using code similar to Google's example:
GoogleCredential googleCredential = new GoogleCredentialBuilder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
.setServiceAccountUser(SERVICE_ACCOUNT_USER)
.setServiceAccountPrivateKeyFromP12File(P12_PRIVATE_KEY_FILE)
.setServiceAccountScopes(Collections.singleton(GLOBAL_USER_AND_ALIAS_SCOPE)
.build();
I see no mention in any examples that I have to explicitly create an access token, so I have been assuming that the above code takes care of that. Is that true?
After that, I successfully create an instance of Directory, then try to retrieve a specific user:
User user = new User();
user = directory.users().get(uid).execute();
That fails, throwing a NullPointerException.
When I inspect the GoogleCredential object right before the call to get the user object, it appears that it does not contain an access token:
accessToken = null
refreshToken = null
What am I missing?
How does one get the access token using the service account model?
Thanks in advance.
Where are you getting your accessToken? Try
credential.refreshToken();
accessToken = credential.getAccessToken();
Also, you should consider running your credentials in the Oauth2 Playground. If it works in the playground, then it's likely something wrong with your implementation.
Andy is correct. The examples for the Google Java API leave out this critical step. At runtime, the Google code throws a NullPointerException with no other details that would identify where it is occurring. Stepping through the debugger in Eclipse made it clear that the token was null in GoogleCredential.
I would like to impersonate a user and add files to the users Google Drive on their behalf from a server process. I've setup a service account and can successfully access the Drive as the service account adding and listing files, etc. using the following code:
/** 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();
public static void main(String[] args) {
try {
GoogleCredential credential =
new GoogleCredential.Builder().setTransport(HTTP_TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId("XXXXX#developer.gserviceaccount.com")
.setServiceAccountScopes(DriveScopes.DRIVE)
.setServiceAccountPrivateKeyFromP12File(new File("c:/junk/key.p12"))
.build();
Drive drive = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential).build();
drive.files().list().execute();
} catch (Exception e) {
e.printStackTrace();
}
This works, however only returns files that are associated to what I assume is associated with the service accounts drive (?).
According to the JavaDoc, GoogleCredential can also be used to impersonate a user by adding the service account users email address as follows:
GoogleCredential credential =
new GoogleCredential.Builder().setTransport(HTTP_TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId("XXXXX#developer.gserviceaccount.com")
.setServiceAccountScopes(DriveScopes.DRIVE)
.setServiceAccountPrivateKeyFromP12File(new File("c:/junk/key.p12"))
.setServiceAccountUser("usera#domain.com") //<-- impersonate user a
.build();
However, when executing this code, the following exception is thrown:
com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad Request
{
"error" : "access_denied"
}
at com.google.api.client.auth.oauth2.TokenResponseException.from(TokenResponseException.java:103)
at com.google.api.client.auth.oauth2.TokenRequest.executeUnparsed(TokenRequest.java:303)
at com.google.api.client.auth.oauth2.TokenRequest.execute(TokenRequest.java:323)
at com.google.api.client.googleapis.auth.oauth2.GoogleCredential.executeRefreshToken(GoogleCredential.java:340)
at com.google.api.client.auth.oauth2.Credential.refreshToken(Credential.java:508)
at com.google.api.client.auth.oauth2.Credential.intercept(Credential.java:260)
at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:796)
at com.google.api.client.googleapis.json.GoogleJsonResponseException.execute(GoogleJsonResponseException.java:198)
at com.google.api.client.googleapis.services.GoogleClient.executeUnparsed(GoogleClient.java:237)
at com.google.api.client.http.json.JsonHttpRequest.executeUnparsed(JsonHttpRequest.java:207)
at com.google.api.services.drive.Drive$Files$List.execute(Drive.java:1071)
Am I missing a step or configuration setting?
Thanks,
David
I found a similar question as mine: Can a Google Apps Admin manage users files with Drive SDK? to mine which has helped me figure out the answer.
The cPanel documentation is a little misleading as it refers to enabling the consumer key and then adding the domain to the Manage API client access screen. This appears to be valid for the gdata api and not the new Google Drive api. By adding the client id as suggested in the other question and granting access to the Drive scope I'm now able to impersonate a user.
Get your admin to add scopes to xxxxx.apps.googleusercontent.com via admin panel:
I added the following to work on spreadsheets:
https://www.googleapis.com/auth/drive
https://docs.google.com/feeds
https://spreadsheets.google.com/feeds