I'm trying to create a new user account via the Google Directory API using the code below. The result I get back is invalid password. What password? Problem with the P12 file I downloaded?
Collection<String> SCOPE = Arrays.asList("https://www.googleapis.com/auth/admin.directory.user");
String serviceAcctEmailAddress = "xxx#developer.gserviceaccount.com";
String serviceAcctUser = "admin#x.com";
final HttpTransport TRANSPORT = new NetHttpTransport();
final JsonFactory JSON_FACTORY = new JacksonFactory();
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(serviceAcctEmailAddress)
.setServiceAccountUser(serviceAcctUser)
.setServiceAccountScopes(SCOPE)
.setServiceAccountPrivateKeyFromP12File(new File("1fc6.p12"))
.build();
Directory directory = new Directory.Builder(TRANSPORT, JSON_FACTORY, credential)
.setApplicationName("API-Project")
.build();
// create user object
User u = new User();
UserName un = new UserName();
un.setGivenName(".");
un.setFamilyName("x#x.com");
u.setName(un);
u.setPassword("Axxx1234");
u.setHashFunction("SHA-1");
u.setPrimaryEmail("x#x.com");
u.setSuspended(false);
u.setAgreedToTerms(true);
Directory.Users.Insert addUser = directory.users().insert(u);
addUser.execute();
Getting the below error:
Exception in thread "main" com.google.api.client.googleapis.json.GoogleJsonResponseException: 400 Bad Request
{
"code" : 400,
"errors" : [ {
"domain" : "global",
"message" : "Invalid Password",
"reason" : "invalid"
} ],
"message" : "Invalid Password"
}
at com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:145)
Thanks for any helping getting started!
Confirm the password you set for the user meets your domain's password policy.
It appears I was setting the hash function to SHA-1 format but I wasn't sending the password in that format. Once I removed the hash function it started working correctly.
Related
I created a service account to use Google chat (not bot) from a local java client. My objective is to migrate flowdock data to Google chat.
I tried different means to get access token using service account and looked at several docs and forums...
Certificate is loaded with all certificate informations. But access token stays null.
public class ChatScopes {
static final String CHAT_SPACES = "https://chat.googleapis.com/v1/spaces";
static final String CLOUD_AUTH = "https://www.googleapis.com/auth/cloud-platform";
static final String CHAT_MESSAGES = "https://www.googleapis.com/chat.messages.create";
static final String CHAT_SPACES_2 = "https://www.googleapis.com/chat.spaces.create";
static final List<String> scopes = Lists.newArrayList(
ChatScopes.CLOUD_AUTH,
ChatScopes.CHAT_SPACES,
ChatScopes.CHAT_SPACES_2,
ChatScopes.CHAT_MESSAGES);
}
...
GoogleCredentials credentials = GoogleCredentials
.fromStream(AuthenticationService.class.getResourceAsStream(CERT_FILE))
.createScoped(ChatScopes.scopes);
1.a
AccessToken token = credentials.refreshAccessToken(); // IOException: Error parsing token refresh response. Expected value access_token not found.
1.b
AccessToken token = credentials.getAccessToken(); // token is null
Update with example Service account##
GoogleCredential credentials = GoogleCredential
.fromStream(AuthenticationService.class.getResourceAsStream(CERT_FILE_2))
.createScoped(Collections.singleton(ChatScopes.CHAT_SPACES));
return new HangoutsChat.Builder(httpTransport, JSON_FACTORY, credentials )
.setApplicationName(APP_NAME)
.build();
.build();
response when requesting using hangoutsChat because access token stays null (with or without calling refresh and/or getAccessToken)
GET https: //chat.googleapis.com/v1/spaces
{
"code": 401,
"details": [
{
"#type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "CREDENTIALS_MISSING"
}
],
"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"
}
It seems we need some clean code crafting here. The error came in fact from wrong scopes declaration. Adding chat bot (why as I don't need bot ?) made me advance.
Scopes
public class ChatScopes {
static final String CLOUD_SCOPE = "https://www.googleapis.com/auth/cloud-platform";
static final String CHAT_SCOPE = "https://www.googleapis.com/auth/chat";
static final String CHAT_BOT_SCOPE = "https://www.googleapis.com/auth/chat.bot";
static final List<String> scopes = Lists.newArrayList(
CLOUD_SCOPE,
CHAT_SCOPE,
CHAT_BOT_SCOPE);
I have a desktop Java app that I am migrating from Google Contacts API to People API. I have some of it working. For example, I can retrieve contact information. But when I tried to create a new contact, I get the following error:
com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden
POST https://people.googleapis.com/v1/people:createContact
{
"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"
}
Here's the relevant code:
protected void createContact() throws Exception {
Credential credential = authorize(PeopleServiceScopes.CONTACTS, "people");
PeopleService service = new PeopleService.Builder(
httpTransport, JSON_FACTORY, credential).setApplicationName(APPLICATION_NAME).build();
Person contactToCreate = new Person();
List<Name> names = new ArrayList<Name>();
names.add(new Name().setGivenName("John").setFamilyName("Doe"));
contactToCreate.setNames(names);
Person createdContact = service.people().createContact(contactToCreate).execute();
System.out.println("CREATED Contact: " + createdContact.getNames().get(0).getDisplayName());
}
protected Credential authorize(String scope, String subDir) throws Exception {
File dataStoreDir = new File(System.getProperty("user.home"), ".store/myapp/" + cfg.dataStore + "/" + subDir);
// initialize the transport
httpTransport = GoogleNetHttpTransport.newTrustedTransport();
// initialize the data store factory
dataStoreFactory = new FileDataStoreFactory(dataStoreDir);
// load client secrets
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY,
new InputStreamReader(SyncMgr.class.getResourceAsStream("/client_secrets.json")));
if (clientSecrets.getDetails().getClientId().startsWith("Enter")
|| clientSecrets.getDetails().getClientSecret().startsWith("Enter ")) {
System.out.println(
"Enter Client ID and Secret from https://code.google.com/apis/console/?api=calendar "
+ "into /client_secrets.json");
System.exit(1);
}
// set up authorization code flow
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
httpTransport, JSON_FACTORY, clientSecrets,
Collections.singleton(scope)).setDataStoreFactory(dataStoreFactory).build();
// authorize
return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize(cfg.gUser);
}
When I first ran it, I had the scope set to CONTACTS_READONLY. And I got the consent screen. But then I changed the scope to CONTACTS when I added the code to create a new contact. And that's when I got the ACCESS_TOKEN_SCOPE_INSUFFICIENT error.
I saw in another post that I need to force your app to reauthorize the user when you change the scope, so that you get the consent screen again. But I'm not sure how to do that. Any suggestions?
Thanks.
UPDATE 1/4/22
I tried Gabriel's suggestion of removing access to the application. After removing access, I ran the application again. This time I got this error on the execute() call:
com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad Request
POST https://oauth2.googleapis.com/token
{
"error" : "invalid_grant",
"error_description" : "Token has been expired or revoked."
}
And even the execute() statement that worked before to retrieve contacts is giving the same error now.
My application also used the Calendar API. I didn't touch that code. But when I try to use it, I get the same "invalid_grant" error. What do I do now?
You appear to be using the People.createContact method. If we take a look at the documentation we will see that this method requires a consent to the following scope of permissions from the user
Now if we check your code you apear to be using
Credential credential = authorize(PeopleServiceScopes.CONTACTS, "people");
Which is the exact scope needed. But you oringally had readonly there. So when your code ran the first time the user authorized to the read only scope and not the full contacts scope and your stuck.
The key here is this section of code.
// set up authorization code flow
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
httpTransport, JSON_FACTORY, clientSecrets,
Collections.singleton(scope)).setDataStoreFactory(dataStoreFactory).build();
// authorize
return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize(cfg.gUser);
Kindly note I am not a Java developer I am a .net developer. The libraries are very close and i have been helping with questions this in both languages for years.
dataStoreFactory is where the consent from the user is stored. There should be a json file some where in your directory structure with the users name associated with it this is how your system reloads it. When your code runs it will look for a file in that directory with cfg.gUser name.
There should be a way in the Java client library to force it to rerequest authorization of the user. prompt type force. But i will have to look around to see how to do it in java.
The easiest solution now would be to find that directory and delete the file for the user or just change the users name cfg.gUser to cfg.gUser +"test" or something this will cause the name to change and the file name as well. Forcing it to prompt the user for authorization again.
This time when it requests consent take note which scope of permissions it asks for.
Token has been expired or revoked.
This is probably due to the fact that your refresh tokens are expiring. When your application is in the testing phase the refresh tokens are expired or revoked automatically by google after seven days.
This is something new and something that Google added in the last year or so. Unfortunately the client libraries were not designed to request access again if the refresh token was expired in this manner.
If you are looking to retrieve the consent screen again you can remove access to your application from your account settings by following the steps in this documentation and then try to authorize the app again. As you mentioned, the error received is due to the scope that was granted with authorization was CONTACTS_READONLY instead of CONTACTS when checking the authorization scope for this specific create contacts method.
I have been struggling with this for over a week.
I'm trying to write backend code in Java to manage users (create/add/delete) in my domain.
I keep seeing the following error:
{
"code" : 403,
"errors" : [ {
"domain" : "global",
"message" : "Not Authorized to access this resource/api",
"reason" : "forbidden"
} ],
"message" : "Not Authorized to access this resource/api"
}
I have been using the recommended Google Client API for Java. I have done the usual:
Created service account
Performed Domain wide delegation on the service account with the following scope - https://www.googleapis.com/auth/admin.directory.user
Enabled the admin api on the project
Written the code below (read somewhere that the JSON credential file doesn't work without the following hack)
GoogleCredential gcFromJson = GoogleCredential.fromStream(new FileInputStream(CREDENTIALS_FILE_PATH),httpTransport, JSON_FACTORY).createScoped(scopes);
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(gcFromJson.getTransport())
.setJsonFactory(gcFromJson.getJsonFactory())
.setServiceAccountId(gcFromJson.getServiceAccountId())
.setServiceAccountPrivateKey(gcFromJson.getServiceAccountPrivateKey())
.setServiceAccountScopes(gcFromJson.getServiceAccountScopes())
.build();
//credential.refreshToken();
Directory directory = new Directory.Builder(httpTransport, JSON_FACTORY, null)
.setApplicationName("My App Name")
.setHttpRequestInitializer(credential)
.build();
User user = new User();
// populate are the required fields only
UserName name = new UserName();
name.setFamilyName("Blogs");
name.setGivenName("Jo");
user.setName(name);
user.setPassword("password101");
user.setPrimaryEmail("jo.blogs#<my actual domain>.org");
user.setEmails("jo.blogs#<my actual domain>.org");
// requires DirectoryScopes.ADMIN_DIRECTORY_USER scope
user = directory.users().insert(user).execute();
return user
Please, somebody, anybody help!
There is one suggested way to create a simple Java command-line application that makes requests to the Directory API. Did you follow all these steps?
Java Quickstart
My app first uses the Cognito LOGIN endpoint to obtain an Authorization Code. It then uses the TOKEN endpoint to try and obtain tokens (id_token, access_token, refresh_token) but that fails with unauthorized_client.
I do not understand why, the same client is used to access the LOGIN, and that succeeded in returning an authorization code. I'm following the documentation for the TOKEN endpoint
string clientId = ...
string clientSecret = ...
Uri redirectUri = new Uri("myapp://myhost");
string authorization_code = ... // obtained via HTTP GET on LOGIN endpoint
string accessTokenUrl = "https://<domain>.auth.<region>.amazoncognito.com/oauth2/token";
var queryValues = new Dictionary<string, string>
{
{ "grant_type", "authorization_code" },
{ "code", authorization_code },
{ "redirect_uri", redirectUri.AbsoluteUri },
{ "client_id", clientId},
};
using (HttpClient client = new HttpClient())
{
// Authorization Basic header with Base64Encoded (clientId::clientSecret)
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(
string.Format("{0}:{1}",
clientId,
clientSecret))));
// Url Encoded Content
var content = new FormUrlEncodedContent(queryValues);
// HTTPS POST
HttpResponseMessage response = await client.PostAsync(accessTokenUrl, content).ConfigureAwait(false);
string text = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
// test = {"error" : "unauthorized_client"}
}
The problem is two-fold:
1- System.Uri.AbsoluteUri adds a trailing / in the returned string so that my redirectUri becomes myapp://myhost/ instead of myapp://myhost
2- AWS Cognito TOKEN endpoint does not accept trailing / in a redirectURI.
The solution:
I now call redirectUri.OriginalUri instead of redirectUri.AbsoluteUri where I build the query to preserve the redirectUri as it was when I built it.
(I don't really have control over this since in my case Xamarin.Auth.OAuthAuthenticator2 calls Uri.AbsoluteUri on my behalf and transforms the redirectUri string I gave it, so I'm going to have to fix Xamarin.Auth).
i try to register a user in my amazon cognito user pool with username and password from my java backend but i always get the error:
Unable to verify secret hash for client
in the documentation i don't found any information how to pass the clientSecret in the register request and i don't like to create an (backend) app without a clientSecret.
My code looks like this
identityProvider = AWSCognitoIdentityProviderClientBuilder.standard().withCredentials(new AWSStaticCredentialsProvider(awsCreds)).withRegion(Regions.EU_CENTRAL_1).build();
Map<String, String> authParameters = new HashMap<>();
authParameters.put("USERNAME", "username");
authParameters.put("PASSWORD", "password");
authParameters.put("SECRET_HASH", "secret copy and paste from the aws console"); // i read in a forum post, that this should work
AdminInitiateAuthRequest authRequest = new AdminInitiateAuthRequest();
authRequest.withAuthFlow(AuthFlowType.ADMIN_NO_SRP_AUTH);
authRequest.setAuthParameters(authParameters);
authRequest.setClientId("clientId");
authRequest.setUserPoolId("userPoolId");
AdminInitiateAuthResult authResponse = identityProvider.adminInitiateAuth(authRequest);
Thanks
Marcel
To register users you should use the SignUp API. The secret hash can be calculated as follows in Java:
public String calculateSecretHash(String userPoolclientId, String userPoolclientSecret, String userName) {
if (userPoolclientSecret == null) {
return null;
}
SecretKeySpec signingKey = new SecretKeySpec(
userPoolclientSecret.getBytes(StandardCharsets.UTF_8),
HMAC_SHA256_ALGORITHM);
try {
Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
mac.init(signingKey);
mac.update(userName.getBytes(StandardCharsets.UTF_8));
byte[] rawHmac = mac.doFinal(userPoolclientId.getBytes(StandardCharsets.UTF_8));
return Encoding.encodeBase64(rawHmac);
} catch (Exception e) {
throw new RuntimeException("Error while calculating ");
}
}
Can you please elaborate your use case of creating users from your backend instead of directly calling Amazon Cognito from your clients?
Edit: We have updated our documentation to include a section about how to compute the secret hash.
The following code works perfectly:
AdminInitiateAuthRequest adminInitiateAuthRequest = new AdminInitiateAuthRequest().withAuthFlow(AuthFlowType.ADMIN_NO_SRP_AUTH).withClientId("<ID of your client application>").withUserPoolId("<your user pool ID>")
.addAuthParametersEntry("USERNAME", "<your user>").addAuthParametersEntry("PASSWORD", "<your password for the user>");
AdminInitiateAuthResult adminInitiateAuth = identityProvider.adminInitiateAuth(adminInitiateAuthRequest);
System.out.println(adminInitiateAuth.getAuthenticationResult().getIdToken());