i tries to add member to my google sheet using java code, but it doesn't work.
I use OAuthor 2.
Can anyone help me with this problem? Thanks very much.
My code:
public static void main(String... args) throws IOException, GeneralSecurityException {
// Build a new authorized API client service.
private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
final String spreadsheetId = "10gLncj6bGmm-UcXP1vztsbv23CD85GNG95zjDgZ8HBA";
JsonBatchCallback<Permission> callback = new JsonBatchCallback<Permission>() {
#Override
public void onFailure(GoogleJsonError e,
HttpHeaders responseHeaders)
throws IOException {
// Handle error
System.err.println(e.getMessage());
}
#Override
public void onSuccess(Permission permission,
HttpHeaders responseHeaders)
throws IOException {
System.out.println("Permission ID: " + permission.getId());
}
};
Drive driveService = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT))
.setApplicationName(APPLICATION_NAME)
.build();
BatchRequest batch = driveService.batch();
Permission userPermission = new Permission()
.setType("user")
.setRole("reader")
.setEmailAddress("trilo10101990#gmail.com");
driveService.permissions().create(spreadsheetId, userPermission)
.setFields("id")
.queue(batch, callback);
batch.execute();
}
Out put :
Insufficient Permission: Request had insufficient authentication scopes.
Insufficient Permission: Request had insufficient authentication scopes.
Means that the user you have authenticated the application with has not granted your application enough scopes to access the data you are trying to access.
You appear to be using Permissions.create this method requires that the user be authorized with one of the following scopes
You didn't post all of your authorization code You probably have something like this.
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow
.Builder(httpTransport, JSON_FACTORY, clientSecrets,
DriveScopes.all()).setDataStoreFactory(dataStoreFactory)
.build();
The trick is to look for which scope you are requesting in the case of the code above it is requesting full access to google drive. You need to check your scope and ensure that you have requested full access.
After that you need to remove the users consent to your application and force it to request access of the user again. The user needs to see the consent screen in order to verify their consent.
Related
Does anyone know the correct setup for the cloud project and redirect URL within the application for the following case?
Setup:
Spring + Apache Wicket
The application is installed on a server (Windows, Linux*) and accessed on the intranet via browser.
*) with or without desktop
Requirements:
Access to one or more Gmail-Accounts to retrieve emails, mark emails as read and move emails to trash
Credentials are stored for each account separately on the server
Creation of the access is done on a client by an admin user in the browser
Consent for an account is done only once on creation, emails are retrieved in a background thread (no user interaction, token is refreshed automatically)
No additional setups on the clients (e.g. changing the host-file, running a background-process/listener); Client could also be a mobile device accessing the intranet
Scopes:
Non-Restricted: userinfo.email
Restricted: gmail.modify
Cloud projects setups/attempts:
Cloud project: Desktop-App; Application: AuthorizationCodeInstalledApp.authorize - Does not work - the consent screen is opened on the server if this is used
Cloud project: Desktop-App; Application: urn:ietf:wg:oauth:2.0:oob as redirect url and popup on the client - Worked but Google is discontinuing oob
Current: Cloud project: Web-App with a public redirect url; Application: redirected to our website - only to show the auth code, which can be pasted in the application open in the browser
public String getAuthorizationUrl(String clientId, String clientSecret, String credentialPath)
{
final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance();
final List<String> SCOPES =
Arrays.asList(new String[] {GmailScopes.GMAIL_MODIFY, Oauth2Scopes.USERINFO_EMAIL});
Details details = new Details();
details.setClientId(clientId);
details.setClientSecret(clientSecret);
GoogleClientSecrets clientSecrets = new GoogleClientSecrets();
clientSecrets.setInstalled(details);
// Build flow and trigger user authorization request.
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES)
.setDataStoreFactory(new FileDataStoreFactory(new File(credentialPath)))
.setApprovalPrompt("force")
.setAccessType("offline")
.build();
/* approval prompt and access type were not needed for desktop-app;
* refresh token was generated anyway, they had to be added for web-app
* to get a refresh token */
String redirUri = "https://example.com/redirect";
AuthorizationCodeRequestUrl authorizationUrl =
flow.newAuthorizationUrl().setRedirectUri(redirUri);
return authorizationUrl.build();
}
Google Oauth verification:
Google says that according to the generated traffic, the app is running on a web server and we need to change it to a local URL, otherwise we need a security assessment because the data is stored on a web server.
While it's technically true that it's running on a web server, it's an intranet server.
It's not possible to define a fixed local URL since the servers IP could be different for each user that is installing the app on his server.
You have several issues here. The first is that you are using a desktop application to run a web app. GoogleAuthorizationCodeFlow.Builder is designed for use with installed apps desktop apps or console applications. Its not designed to be run hosted on a web server.
Follow the following example Web server applications
public class CalendarServletSample extends AbstractAuthorizationCodeServlet {
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
// do stuff
}
#Override
protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
GenericUrl url = new GenericUrl(req.getRequestURL().toString());
url.setRawPath("/oauth2callback");
return url.build();
}
#Override
protected AuthorizationCodeFlow initializeFlow() throws IOException {
return new GoogleAuthorizationCodeFlow.Builder(
new NetHttpTransport(), GsonFactory.getDefaultInstance(),
"[[ENTER YOUR CLIENT ID]]", "[[ENTER YOUR CLIENT SECRET]]",
Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory(
DATA_STORE_FACTORY).setAccessType("offline").build();
}
#Override
protected String getUserId(HttpServletRequest req) throws ServletException, IOException {
// return user ID
}
}
public class CalendarServletCallbackSample extends AbstractAuthorizationCodeCallbackServlet {
#Override
protected void onSuccess(HttpServletRequest req, HttpServletResponse resp, Credential credential)
throws ServletException, IOException {
resp.sendRedirect("/");
}
#Override
protected void onError(
HttpServletRequest req, HttpServletResponse resp, AuthorizationCodeResponseUrl errorResponse)
throws ServletException, IOException {
// handle error
}
#Override
protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
GenericUrl url = new GenericUrl(req.getRequestURL().toString());
url.setRawPath("/oauth2callback");
return url.build();
}
#Override
protected AuthorizationCodeFlow initializeFlow() throws IOException {
return new GoogleAuthorizationCodeFlow.Builder(
new NetHttpTransport(), GsonFactory.getDefaultInstance()
"[[ENTER YOUR CLIENT ID]]", "[[ENTER YOUR CLIENT SECRET]]",
Collections.singleton(CalendarScopes.CALENDAR)).setDataStoreFactory(
DATA_STORE_FACTORY).setAccessType("offline").build();
}
#Override
protected String getUserId(HttpServletRequest req) throws ServletException, IOException {
// return user ID
}
}
installing app.
You have stated this
It's not possible to define a fixed local URL since the servers IP could be different for each user that is installing the app on his server.
Which implies to me that you are giving the code for this app directly to your users with out it being compiled. This includes your credeitnals.json file. YOu may not do this this is against the TOS. Can I really not ship open source with Client ID?
Asking developers to make reasonable efforts to keep their private keys private and not embed them in open source projects.
You should be instructing your users in how to create their own client id and client secrete. in order to get their own creditnals.json file.
They can then supply their own ip address of their server.
In which case your issue with verification is no longer an issue. You dont need to verfy for them. They should be doing that themselves.
push back on internal app
When your users go to verification their app make sure that they are clear with Google that this is an internal app. Hosted on their intranet. They should not need verification.
I'm getting this error when Im trying to write data to my spreadsheet in Google sheets.
{"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"}
The Java code:
public static void updateData(String sheetName, String cellLocation, String newValue) throws Exception {
final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
Sheets service = new Sheets.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT))
.setApplicationName(APPLICATION_NAME)
.build();
ValueRange body = new ValueRange()
.setValues(Arrays.asList(Arrays.asList(newValue)));
UpdateValuesResponse result =
service.spreadsheets().values().update(spreadsheetId, cellLocation, body)
.setValueInputOption("RAW")
.execute();
System.out.printf("%d cells updated.", result.getUpdatedCells());
}
Thank You!
I have tried to make my spreadsheet public.
Your program uses spreadsheets().values().update() which requires OAuth scopes.
This is the reason why the "Request had insufficient authentication scopes" error occurs. Refer to https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets.values/update?hl=en for more details.
This link should help you in authorizing the application.
https://developers.google.com/sheets/api/quickstart/java?hl=en
I have a web app where users have to authenticate using Google sign-in. I do this because I need to grab their email address. When they fill out the fields on the page, all that data is stored in a google sheet alongside their email address (for auditing purposes incase something is askew with the data). Unfortunately what's happening is that if user A signs in, and does some work and at the same time user B logs in, when user A submits data, they will be submitting user B's email address (as does user B). In short, the latest person to log in, that email address is used. There is no database and I'm not storing any cookies. When they refresh the page, they have to re-authenticate. I am using Angular 7 and Java. Here is the code that I used:
ngOnInit() {
gapi.load('auth2', () => {
this.auth2 = gapi.auth2.init({
client_id: 'CLIENT_ID_HERE',
// Scopes to request in addition to 'profile' and 'email'
scope: 'https://www.googleapis.com/auth/spreadsheets'
});
});
}
signInWithGoogle(): void {
this.auth2.grantOfflineAccess().then((authResult) => {
this.authCode = authResult['code'];
this.fetchData();
});
}
authCode is bound to the child component so it can be passed as query param to the java code for google auth.
this.seriesService.submitSeriesData(matchList, this.authToken).subscribe(res => {.....);
The google auth java code is so:
private static final String APPLICATION_NAME = "Google Sheets API Java";
private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
private static final List<String> SCOPES = Collections.singletonList(SheetsScopes.SPREADSHEETS);
private static final String CLIENT_SECRET_DIR = "/client_secret.json";
private static GoogleTokenResponse tokenResponse = null;
public static String getEmailAddress() throws IOException {
GoogleIdToken idToken = tokenResponse.parseIdToken();
GoogleIdToken.Payload payload = idToken.getPayload();
String email = payload.getEmail();
return email;
}
public static Sheets getSheetsService1(String token, String redirectUri) throws IOException, GeneralSecurityException {
// Exchange auth code for access token
InputStream in = GoogleAuthUtil.class.getResourceAsStream(CLIENT_SECRET_DIR);
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));
tokenResponse =
new GoogleAuthorizationCodeTokenRequest(
new NetHttpTransport(),
JacksonFactory.getDefaultInstance(),
"https://www.googleapis.com/oauth2/v4/token",
clientSecrets.getDetails().getClientId(),
clientSecrets.getDetails().getClientSecret(),
token,
redirectUri)
.execute();
String accessToken = tokenResponse.getAccessToken();
GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken);
Sheets service = new Sheets.Builder(new NetHttpTransport(), JacksonFactory.getDefaultInstance(), credential)
.setApplicationName("MY APP HERE")
.build();
return service;
}
And the endpoint:
#RequestMapping(value="series/data", method = RequestMethod.POST, consumes="application/json")
public boolean submitSeriesMatchData(#RequestBody(required=true) SubmitStatsDto request) throws IOException, GeneralSecurityException, Exception {
if (service == null) {
service = GoogleAuthUtil.getSheetsService1(request.getToken(), this.redirectUri);
}
......
}
1) User clicks on the google sign in button
2) They select email and auth with google
3) I receive an auth code back and store it in ng.
4) Every REST call is passed said token to auth with google, and every endpoint calls getSheetsService1 which authenticates w/ token. (multiple endpoints, I only showed one above)
5) I get email from that tokenResponse.
Any ideas? This site will not have a database/users/local logins. Thank you.
I'm working on a project that uses the Google Apps Reseller API (Found here).
I'm running into a 403 Forbidden Exception.
Code (most of it origins from the Google Codelab Example Here:
try {
try {
Reseller service = GoogleResellerApiUtil.getResellerService();
Customer customerRecord = service.customers().get("acme.com").execute(); //crashes here
// "acme.com" is also used in the example from Google
System.out.println(customerRecord.toString());
} catch (GoogleJsonResponseException e) {
e.printStackTrace();
}
And this is the class I use to connect to the API.
I've provided a p12 file and it uses the service account, when calling the API it is impersonating one of the super admins, so it should be allowed to make all the calls.
At the moment I'm only using the read-only scope.
public class GoogleResellerApiUtil {
/** HTTP_TRANSPORT */
private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
/** JSON Factory*/
private static final JsonFactory JSON_FACTORY = new JacksonFactory();
/** Service Account Email */
public static final String SERVICE_ACCOUNT_EMAIL = "****#appspot.gserviceaccount.com";
/** P12 File Location */
public static final String PRIVATE_KEY_FILE = "WEB-INF/key.p12";
/** Reseller Admin Account to impersonate */
public static final String RESELLER_ADMIN = "**.**#**.com";
/** Scopes */
public static final List<String> SCOPES = Arrays.asList(ResellerScopes.APPS_ORDER_READONLY);
/** Application name. */
private static final String APPLICATION_NAME = "**-subscription-portal";
/** Logger */
private final static Logger LOGGER =
Logger.getLogger(GoogleResellerApiUtil.class.getName());
public static GoogleCredential getCredentials() throws IOException {
GoogleCredential credentials = null;
try {
credentials = new GoogleCredential.Builder()
.setTransport(HTTP_TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
.setServiceAccountScopes(SCOPES)
.setServiceAccountUser(RESELLER_ADMIN)
.setServiceAccountPrivateKeyFromP12File(new File(PRIVATE_KEY_FILE))
.build();
} catch (GeneralSecurityException e) {
e.printStackTrace();
}
System.out.println("credential has been build, returning credential "); //this gets printed, so I think the credentials are valid?
return credentials;
}
/**
* Build and return an authorized Reseller client service.
* #return an authorized Reseller client service
* #throws IOException
*/
public static Reseller getResellerService() throws Exception {
Credential credential = getCredentials();
return new Reseller.Builder(
HTTP_TRANSPORT, JSON_FACTORY, credential)
.setApplicationName(APPLICATION_NAME)
.build();
}
}
But I get the following error message when making the call:
com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 OK
{
"code" : 403,
"errors" : [ {
"domain" : "global",
"message" : "Forbidden",
"reason" : "forbidden"
} ],
"message" : "Forbidden"
}
at com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:146)
etc. etc. etc.
It is noted in Reseller API: Manage Subscriptions that
Note: If the customerAuthToken is not valid or has expired, the API response returns a 403 "Forbidden" error.
To solve the issue, please make sure that requests must be authorized by an authenticated user who has access to the data. As also noted in Reseller API: Authorizing
Note: The user granting permission for the Reseller API must be a domain super administrator.
In addition to that, it was suggested in Token expiration that you write your code to anticipate the possibility that a granted token might no longer work. A token might stop working for one of these reasons:
The user has revoked access.
The token has not been used for six months.
The user changed passwords and the token contains Gmail scopes.
The user account has exceeded a certain number of token requests.
Hope that helps!
I try to get the userinfo after successfully authenticate with a gmail account (tok is a valid token):
GoogleCredential credential2 = new GoogleCredential.Builder()
.setTransport(TRANSPORT).setJsonFactory(JSON_FACTORY)
.setClientSecrets(CLIENT_ID, CLIENT_SECRET)
.setRequestInitializer((new HttpRequestInitializer() {
#Override
public void initialize(HttpRequest request)
throws IOException {
request.getHeaders().setAuthorization("Bearer ".concat(tok));
}
}))
.build();
Oauth2 userInfoService = new Oauth2.Builder(TRANSPORT,
JSON_FACTORY, credential2.getRequestInitializer())
.setApplicationName(APPLICATION_NAME).build();
Userinfo userInfo = userInfoService.userinfo().get().execute();
logger.warn("User email: {}", userInfo.getEmail());
logger.warn("User gender: {}", userInfo.getGender());
logger.warn("User complet name: {} - {}", userInfo.getFamilyName(), userInfo.getName());
But logs display 'null' for all fields, the json data returned contains only the id:
{
"id": "113695880661351193041"
}
What i'm supposed to do ? Add a special scope to do this? I tried it several times without success, just by adding scope=https://www.googleapis.com/auth/userinfo.profile as url parameter, maybe that's wrong ?
Hope someone can help or know how to add scopes to my request and get the correct response from this service.
This works for me :
Credential credential = OAuth2Utils.newFlow().loadCredential(userId);
Oauth2 service = new Oauth2.Builder(OAuth2Utils.HTTP_TRANSPORT, OAuth2Utils.JSON_FACTORY, credential).setApplicationName("appname").build();
UserInfo userInfo = service.userinfo().get().execute();
These are some of the properties that are returned :
userInfo.getBirthday()
userInfo.getFamilyName()
userInfo.getGender()
userInfo.getGivenName()
userInfo.getHd()
userInfo.getLink()
The utility class OAuth2Utils referred in the code :
/** Global instance of the HTTP transport. */
public final static HttpTransport HTTP_TRANSPORT = new UrlFetchTransport();
/** Global instance of the JSON factory. */
public final static com.google.api.client.json.JsonFactory JSON_FACTORY = new com.google.api.client.json.jackson2.JacksonFactory();
public static GoogleAuthorizationCodeFlow newFlow() throws IOException {
return new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY, getClientCredential(),
Arrays.asList(SCOPES)).setCredentialStore(new OAuth2CredentialStore()).setAccessType("offline")
.setApprovalPrompt("force").build();
}