I'm creating a channel that receive changes on users that are on my application. The main problem is that after 2-3 webhooks, I receive an error that says that user has exceeded the quota limits.
That has no sense, because I only received 2 post message (I saw it on ngrok).
I've went on google console on drive API and quota. Each time I receive a webhook the amount of queries is increased by 500. So, when a user make two changes and I receive two webhooks, the number of queries exceed the 1000 allowed by google and I receive that error.
That's the code where I enable the channel:
#GET
#Path("/enable")
public void enable(#Context HttpServletRequest request, #Context HttpServletResponse response) throws IOException {
Credential credential = initFlow().loadCredential("user");
Drive service = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential).setApplicationName(APPLICATION_NAME).build();
Channel channel = new Channel();
channel.setId(UUID.randomUUID().toString());
channel.setType("web_hook");
channel.setAddress("https://389825dc.ngrok.io/GDriveRest/app/gdrive/webhook");
StartPageToken page = service.changes().getStartPageToken().execute();
GDrive.savedPageToken = page.getStartPageToken();
service.changes().watch(savedPageToken, channel).execute();
}
And the following one is the webhook:
#POST
#Path("/webhook")
public void webhook(#Context HttpServletRequest request, #Context HttpServletResponse response) throws IOException {
Credential credential = initFlow().loadCredential("user");
Drive service = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential).setApplicationName(APPLICATION_NAME).build();
String pageToken = savedPageToken;
while (pageToken != null) {
ChangeList changes = service.changes().list(pageToken).execute();
for (Change change : changes.getChanges()) {
Log.info("Change found for file: " + change.getFileId());
}
if (changes.getNewStartPageToken() != null) {
savedPageToken = changes.getNewStartPageToken();
}
pageToken = changes.getNewStartPageToken();
}
response.setStatus(200);
}
This is the error:
Caused by: com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden
{
"code" : 403,
"errors" : [ {
"domain" : "usageLimits",
"message" : "User Rate Limit Exceeded",
"reason" : "userRateLimitExceeded"
} ],
"message" : "User Rate Limit Exceeded"
}
Why this is happening?
There was an error in the code. I had to change the following:
PageToken = changes.getNewStartPageToken();
to
pageToken = changes.getNextPageToken();
The huge amount of queries was because I was requesting on every loop a new start page token, forcing an infinite loop.
The error you have encountered was discussed in this guide.
Suggested actions:
Raise the per-user quota in the Developer Console project.
If one user is making a lot of requests on behalf of many users of a G Suite domain, consider a Service Account with authority delegation
(setting the quotaUser parameter).
Use exponential backoff.
You can also try visiting this SO post for related issue.
Related
I'm using Firebase authentication together with Cloud Endpoints Frameworks.
In context of this, I have two questions which belong together:
In my Android app I'm requesting the access token after a successfully login in the following way:
FirebaseUser user = mFirebaseAuthenticator.getCurrentUser();
user.getIdToken(true).addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
public void onComplete(#NonNull Task<GetTokenResult> task) {
if (task.isSuccessful()) {
mIDToken = task.getResult().getToken();
Log.d("attempLogin", "GetTokenResult result = " + mIDToken);
} else {
Log.d("attempLogin", "Cannot get token = " + task.getException());
}
}
});
Afterwards I pass the received access token to the automatically generated
endpoints framework client API method allOrdersRequest(...)
OrderCollection orders = allOrdersRequest.setOauthToken(mIDToken).execute();
to execute a valid and authorized backend API call.
1st question:
The received access token has about 800 characters, which is in my opinion
relatively too much. It's almost 1kb which has to be send with each backend API method request. Is my assumption correct, or should (or even can I) change the access token size in Firebase's console?
2nd question:
Is it the right way to pass the received token to the setOauthToken() method of the endpoints framework client API to perform an authorized API request, or must I manipulate each time the httpheader of the allOrdersRequest()?
I found the correct way to authorize a cloud endpoints API request:
Using the method setOauthToken() from one of my generated cloud endpoints client API requests (in this example the method allOrdersRequest() is a backend api method) is the wrong way.
Instead, it is neccessary to specify the "Authorization" http header field of typ bearer and assign to it the requested Firebase access token (idToken) in the REST API request (endpoints client API)
Here is an example:
// Initiate cloud endpoints client API builder
Endpoint.Builder endpoint = new Endpoint.Builder(AndroidHttp.newCompatibleTransport(), new GsonFactory(), null);
endpoint.setRootUrl("https://my_project_id.appspot.com/_ah/api/");
endpoint.setApplicationName("my_project_id");
Endpoint service = endpoint.build();
HttpHeaders authorizationHeader = new HttpHeaders();
authorizationHeader.setAuthorization("Bearer " + mAccessToken);
// Model instance
OrderRequest orderRequest = new OrderRequest();
orderRequest.setBagId(35);
orderRequest.setPriority(9);
orderRequest.setCustomer("foo#bar.com");
try {
Endpoint.ProcesOrderRequest request = service.procesOrderRequest(orderRequest);
request.setRequestHeaders(authorizationHeader);
Order order = request.execute();
Log.d("ExecuteAPIRequest", "OrderId result = " + order.getOrderId());
} catch (IOException ex) {
System.out.println("Exception caught: " + ex.getMessage());
}
I am currently using Gmail API to send emails on user's behalf. The Mails are sent one by one and the average size of recipients is 500.
I frequently see {
"code" : 500,
"errors" : [ {
"domain" : "global",
"message" : "Backend Error",
"reason" : "backendError"
} ],
"message" : "Backend Error"
}
as well as some occurrences of
{
"code" : 429,
"errors" : [ {
"domain" : "usageLimits",
"message" : "Rate Limit Exceeded",
"reason" : "rateLimitExceeded"
} ],
"message" : "Rate Limit Exceeded"
}
Google has suggested implementing Exponential backoff strategy to resolve these errors. I have implemented below solution, but it doesn't seem to work and is not helping with these errors.Here is my implementation;
public GoogleCredential createCredentialWithRefreshToken(String accessToken, String refreshToken)
{
GoogleCredential credential = new GoogleCredential.Builder().setTransport(new NetHttpTransport())
.setJsonFactory(new JacksonFactory())
.setClientSecrets(Constants.GOOGLE_CLIENT_ID, Constants.GOOGLE_CLIENT_SECRET)
.setRequestInitializer(setHttpTimeout())
.build();
credential.setAccessToken(accessToken).setRefreshToken(refreshToken);
return credential;
}
public HttpRequestInitializer setHttpTimeout() {
return new HttpRequestInitializer() {
#Override
public void initialize(HttpRequest httpRequest) throws IOException {
httpRequest.setUnsuccessfulResponseHandler(new HttpBackOffUnsuccessfulResponseHandler(backOff()));
httpRequest.setConnectTimeout(3 * 60000); // 3 minutes connect timeout
httpRequest.setReadTimeout(3 * 60000); // 3 minutes read timeout
}
private final ExponentialBackOff.Builder BACK_OFF = new ExponentialBackOff.Builder().setInitialIntervalMillis(500);
private BackOff backOff() {
return BACK_OFF.build();
}
};
}
public static Gmail getGmailServiceForGoogleAccount(GoogleAccount googleAcct){
Gmail gmailService = null;
GoogleCredential credential = new Utils().createCredentialWithRefreshToken(googleAcct.getAccess_token(),googleAcct.getRefresh_token());
gmailService = new Gmail.Builder(new NetHttpTransport(),
new JacksonFactory(), credential)
.setApplicationName("test")
.build();
return gmailService;
}
What is wrong with this implementation? Am i implementing the custom HttpRequestInitializer correctly.
Where could i set the log statements to find out if a request is being retried as per Exponential policy?
Please suggest
I see this is an old question, but will leave my answer here in case anyone finds it useful.
The problem with the code is that it is calling .setRequestInitializer() on the GoogleCredential.Builder, which sets the initializer for token requests and not the service API requests.
See the documentation here
Sets the HTTP request initializer for refresh token requests to the token server or null for none.
Instead the initializer should be configured on the Google service client and you can chain it with the Credential response handler to preserve its functionality too.
Something like this should work for the provided example:
public static HttpRequestInitializer requestInitializer(Credential credential) {
return new HttpRequestInitializer() {
#Override
public void initialize(HttpRequest httpRequest) throws IOException {
httpRequest.setConnectTimeout(3 * 60000); // 3 minutes connect timeout
httpRequest.setReadTimeout(3 * 60000); // 3 minutes read timeout
// chain response handler with the handler from the credential
// that handles retries for authentication errors
HttpUnsuccessfulResponseHandler responseHandler =
new HttpBackOffUnsuccessfulResponseHandler(backOff());
httpRequest.setUnsuccessfulResponseHandler((req, res, retry) ->
credential.handleResponse(req, res, retry)
|| responseHandler.handleResponse(req, res, retry));
}
private final ExponentialBackOff.Builder BACK_OFF = new ExponentialBackOff.Builder().setInitialIntervalMillis(500);
private BackOff backOff() {
return BACK_OFF.build();
}
};
}
public static Gmail getGmailServiceForGoogleAccount(GoogleAccount googleAcct){
GoogleCredential credential = new Utils().createCredentialWithRefreshToken(googleAcct.getAccess_token(),googleAcct.getRefresh_token());
return new Gmail.Builder(new NetHttpTransport(), new JacksonFactory(), requestInitializer(credential))
.setApplicationName("test")
.build();
}
Check the Exponential Backoff for Java implementation:
ExponentialBackOff backoff = new ExponentialBackOff.Builder()
.setInitialIntervalMillis(500)
.setMaxElapsedTimeMillis(900000)
.setMaxIntervalMillis(6000)
.setMultiplier(1.5)
.setRandomizationFactor(0.5)
.build();
request.setUnsuccessfulResponseHandler(new HttpBackOffUnsuccessfulResponseHandler(backoff));
Check this SO post for additional reference.
I'm using the appengine endpoints-framework-v2 java sample. In this sample there is an endpoint called getUserEmailFirebase which should authenticated firebase user but it does not work
#ApiMethod(
path = "firebase_user",
httpMethod = ApiMethod.HttpMethod.GET,
authenticators = {EspAuthenticator.class},
issuerAudiences = {#ApiIssuerAudience(name = "firebase",
audiences = {"my-project-id"})}
)
public Email getUserEmailFirebase(User user) throws UnauthorizedException {
if (user == null) {
throw new UnauthorizedException("Invalid credentials");
}
Email response = new Email();
response.setEmail(user.getEmail());
return response;
}
I tried to send the request with an Authorization header inculding the firebase user id token but I'm getting 401 response, Also tried to add 'Bearer ' at the begining of the token but I get the same result. Even tried to follow this appengine guide Authenticating Users (Frameworks) without success.
The token is retrieved by AngularFire SDK using firebaseUser.getIdToken(), I have used the Firebase server sdk to authenticated users successfuly but it doesn't work using the above method
Can you help?
I am following this post: Outlook RestGettingStarted. From my Java app I am trying to get AccessToken and RefreshToken. When I made Authorization code request, it ended into following error:
Sorry, but we’re having trouble signing you in. We received a bad
request.
Additional technical information: Correlation ID:
ed838d66-5f2e-4cfb-9223-a29082ecb26f Timestamp: 2015-08-20 10:20:09Z
AADSTS90011: The 'resource' request parameter is not supported.
NOTE: URL formation is correct as per documentation.
So, I removed "resource" query parameter from my code. And redirected authorize url in browser. On user consent I got authorization code. Using this code I got AccessToken. But when I try to connect with Outlook IMAP server it failed. Java ref Link for details: Java OAuth2
But it gives me error:
[AUTHENTICATIONFAILED] OAuth authentication failed.
NOTE: I added correct scope, and user email.
Then using obtained Access Token I made Mail Rest API call to get Messages from User Inbox. It ended into following error:
HTTP response:
{"error":{"code":"MailboxNotEnabledForRESTAPI","message":"REST API is
not yet supported for this mailbox."}}
Can anyone help me for following:
What is the exact cause for: "AADSTS90011: The 'resource' request parameter is not supported" after following Outlook dev docs.
How to resolve "MailboxNotEnabledForRESTAPI" error.
Is it possible to connect using java mail APIs to Outlook IMAP server with correct AccessToken ?
I ran into this recently, but don't remember which solved it. One main issue is in the documentation in that it is varying. It will tell you to attach "resource", but that is for something else like Azure.
Here is the code I used:
First request to send:
private static final String USER_OAUTH2_AUTHORIZE_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
public String getOAuthDialog(Http.Request request) {
return USER_OAUTH2_AUTHORIZE_URL
+ "?client_id=" + config.getClientId()
+ "&redirect_uri=" + getOutlookLoginRedirect(request)
+ "&response_type=code"
+ "&scope=https%3A%2F%2Foutlook.office.com%2Fmail.send%20" +
"https%3A%2F%2Foutlook.office.com%2Fmail.readwrite%20" +
"offline_access%20openid%20email%20profile"
+ "&state=" + crypto.generateSignedToken();
}
Scope was the hardest thing to figure out. I found a lot of ones that did not work. And it wasn't clear that I needed to separate them with spaces.
Then they will send you a request to your redirect url that was supplied. It will contain a code which you need to exchange for the data you requested in the scope. The redirect url that is supplied needs to be the exact same. Also you need to register the redirect url on your application portal under the Platform->Add Platform->Redirect URI->Add Url
private static final String USER_ACCESS_TOKEN_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
private Map<String, String> sendOutlookUserOAuthRequest(Http.Request request, String code) {
WSClient ws = WS.client();
HttpParameters params = new HttpParameters();
params.put("client_id", config.getClientId(), true);
params.put("client_secret", config.getClientSecret(), true);
params.put("code", code, true);
params.put("redirect_uri", getOutlookLoginRedirect(request), true);
params.put("grant_type", "authorization_code");
String postParams = OAuthUtil.parametersToString(params);
WSRequest wsRequest = ws.url(USER_ACCESS_TOKEN_URL)
.setMethod("POST")
.setContentType("application/x-www-form-urlencoded")
.setBody(postParams);
WSResponse wsResponse = wsRequest.execute().get(10, TimeUnit.SECONDS);
Map<String, String> result = new HashMap<>();
if (wsResponse.getStatus() != HttpStatus.SC_OK) {
return result;
}
JsonNode node = wsResponse.asJson();
if (node.hasNonNull("access_token")) {
result.put("access_token", node.get("access_token").asText());
}
if (node.hasNonNull("refresh_token")) {
result.put("refresh_token", node.get("refresh_token").asText());
}
if (node.hasNonNull("id_token")) {
String[] tokenSplit = node.get("id_token").asText().split("\\.");
if (tokenSplit.length >= 2) {
try {
JSONObject jsonObject = new JSONObject(new String(Base64.getDecoder().decode(tokenSplit[1])));
if (jsonObject.has("name")) {
result.put("name", jsonObject.get("name").toString());
}
if (jsonObject.has("email")) {
result.put("outlookUid", jsonObject.get("email").toString());
} else if (jsonObject.has("preferred_username")) {
result.put("outlookUid", jsonObject.get("preferred_username").toString());
}
} catch (JSONException e) {
log.error("Error extracting outlookUid from id_token: ", e);
}
}
}
return result;
}
Another request that you might need is to update the refresh token:
private String getAccessTokenFromRefreshToken(User user) {
WSClient ws = WS.client();
HttpParameters params = new HttpParameters();
params.put("client_id", config.getClientId(), true);
params.put("client_secret", config.getClientSecret(), true);
params.put("grant_type", "refresh_token");
params.put("refresh_token", user.getOutlookRefreshToken());
String postParams = OAuthUtil.parametersToString(params);
WSRequest wsRequest = ws.url(USER_ACCESS_TOKEN_URL)
.setMethod("POST")
.setContentType("application/x-www-form-urlencoded")
.setBody(postParams);
WSResponse wsResponse = wsRequest.execute().get(10, TimeUnit.SECONDS);
if (wsResponse.getStatus() != HttpStatus.SC_OK) {
log.error("Failure to refresh outlook access token for user: " + user +
". Received status: " + wsResponse.getStatus() + " : " + wsResponse.getStatusText());
return null;
}
JsonNode node = wsResponse.asJson();
if (node.hasNonNull("access_token")) {
String accessToken = node.get("access_token").asText();
return accessToken;
} else {
log.error("Outlook refresh token failure, 'access_token' not present in response body: " + wsResponse.getBody());
return null;
}
}
One issue I ran into that took far longer than I would have hoped was in getting the clientId and clientSecret. This was because the language microsoft uses wasn't the most explicit. Client Id and application id are used interchangeably. The client secret is also the password that you create on the Application Portal, not to be confused with the Private Key that you can generate.
So you actually want the application_id and the password, although they refer to them as client_id and client_secret with no direct indication as to the lines drawn.
This is all assuming you have set up an application on the Outlook Application Portal. https://apps.dev.microsoft.com/
I hope this helps, although I assume you probably already solved this.
I faced the same problem with Java mail. You need to add service principals for your application on the Azure AD.
Find complete steps explained in Medium article Complete guide: Java Mail IMAP OAuth2.0 Connect Outlook | by Ritik Sharma | Dec, 2022.
I am making batch requests for adding members to groups. For this I am using OAuth2.0 and obtaining the object of class type Credential.When executed, the batch.execute() throws a
java.net.SocketTimeoutException : Read timed out
To change the timeout limit, this is the solution I found :
GoogleApps client giving SocketTimeOutException only
But I am not able to change the timeout of the Credential object I am creating.
Credential credential = new AuthorizationCodeInstalledApp(
flow, new LocalServerReceiver()).authorize("user");
NOTE : Credential implements HttpRequestInitializer.
Thanks in advance!
Found this after a long time! There is a separate HttpRequestInitializer object for a batch HTTP and an atomic HTTP request to Google. I had earlier changed the HttpRequestInitializer object of the atomic requests (expecting the same to have affect on batch requests) as below:
new Directory.Builder(
HTTP_TRANSPORT, JSON_FACTORY,new HttpRequestInitializer() {
#Override
public void initialize(HttpRequest httpRequest) throws IOException {
credential.initialize(httpRequest);
httpRequest.setConnectTimeout(3); // 3 minutes connect timeout
httpRequest.setReadTimeout(3); // 3 minutes read timeout
System.out.println("Hello"); // Just to track when a http request is made.
}
}).setApplicationName(APPLICATION_NAME).build();
This resulted in the set read timeout for atomic requests. The batch request, sent using batch.execute() still had the default read timeout.
To change the same for the batch requests, I changed this in my BatchRequest intializaiton :
BatchRequest batch = service.batch(new HttpRequestInitializer() {
#Override
public void initialize(HttpRequest request) throws IOException {
credential.initialize(request);
request.setConnectTimeout(10 * 60000);
request.setReadTimeout(10 * 60000);
System.out.println(request.getReadTimeout() + 2); //Just to track when a batch http request is made.
}
});