access_denied when accessing Gmail API with auth scope https://mail.google.com/ - gmail-api

I am trying to read/write emails/folders inside Gmail mailboxes using the Gmail REST API. When adding the following Google auth scopes, emails can be read from Gmail REST API without any problem:
https://apps-apis.google.com/a/feeds/compliance/audit/,
https://www.googleapis.com/auth/admin.directory.user.readonly, https://www.googleapis.com/auth/gmail.readonly,
https://www.googleapis.com/auth/admin.directory.group.member.readonly, https://www.googleapis.com/auth/admin.directory.group.readonly
Note: The parameter https://www.googleapis.com/auth/gmail.readonly correctly allows one to read from mailboxes.
However, I need to be able to delete emails too. Thus, in line with the documentation at https://developers.google.com/gmail/api/auth/scopes?hl=ja, one simply needs to include https://mail.google.com/ in place of https://www.googleapis.com/auth/gmail.readonly. When adding the following auth scopes:
https://apps-apis.google.com/a/feeds/compliance/audit/,
https://www.googleapis.com/auth/admin.directory.user.readonly, https://mail.google.com/,
https://www.googleapis.com/auth/admin.directory.group.member.readonly, https://www.googleapis.com/auth/admin.directory.group.readonly
... the error outputted is as follows:
2015-07-27 10:27:59 i.c.s.a.cv [DEBUG] failed get labels for user
com.google.api.client.auth.oauth2.TokenResponseException: 403 Forbidden
{
"error" : "access_denied",
"error_description" : "Requested client not authorized."
}
Surely, this is incorrect on the part of Google? What am I missing? Is the documentation incorrect? What auth scope needs to be added?
I am interfacing with the Java Google API Client Library. See: https://developers.google.com/api-client-library/java/google-api-java-client/reference/1.20.0/overview-summary
The delete request is as follows:
public void deleteMessages(Queue<String> messages, GoogleUserAdapter user) throws Exception {
Gmail gmail = getService(user);
JsonBatchCallback<Void> voidCallBack = new JsonBatchCallback<Void>() {
#Override
public void onSuccess(Void t, HttpHeaders responseHeaders) throws IOException {
logger.debug("delete success");
}
#Override
public void onFailure(GoogleJsonError e, HttpHeaders responseHeaders) throws IOException {
logger.debug("failed to delete message:"+e.getMessage());
}
};
while (!messages.isEmpty()) {
if (Thread.currentThread().isInterrupted())
throw new InterruptedException();
BatchRequest batch = gmail.batch();
for (int i = 0; i < MAX_REQUESTS; i++) {
if (messages.isEmpty() || Thread.currentThread().isInterrupted())
break;
gmail.users().messages().delete(user.getId(), messages.poll()).queue(batch, voidCallBack);
}
batch.execute();
}
}
The credential is created as follows:
private GoogleCredential getCredentials(JsonFactory jsonFactory, HttpTransport httpTransport, String impersonateAccount) throws Exception {
Preconditions.checkNotNull(Strings.emptyToNull(impersonateAccount), "Google impersonate account is null");
Preconditions.checkNotNull(Strings.emptyToNull(connection.getServiceAccountId()), "Service Account Email address is null");
Preconditions.checkNotNull(connection.getServiceAccountPrivateKey(), "Service Account Private Key is null");
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountId(connection.getServiceAccountId())
.setServiceAccountScopes(
Arrays.asList(DirectoryScopes.ADMIN_DIRECTORY_USER_READONLY, GmailScopes.MAIL_GOOGLE_COM,
"https://apps-apis.google.com/a/feeds/compliance/audit/",
DirectoryScopes.ADMIN_DIRECTORY_GROUP_MEMBER_READONLY,
DirectoryScopes.ADMIN_DIRECTORY_GROUP_READONLY))
.setServiceAccountUser(impersonateAccount)
.setServiceAccountPrivateKey(connection.getServiceAccountPrivateKey().getPrivateKey())
.build();
setHttpTimeout(credential);
return credential;
}
The exact error that occurs on delete is:
failed to delete message:Insufficient Permission
Jamie

Access denied is caused by a typo in the constant GmailScopes.MAIL_GOOGLE_COM as defined by the Google Java Client API.
The constant returns "https://mail.google.com" and not "https://mail.google.com/" (as it ought to be). Omitting a backslash at the end of the string will result in access denied.
Thus, in the example above, the following service scopes must be set:
https://apps-apis.google.com/a/feeds/compliance/audit/","https://mail.google.com/",DirectoryScopes.ADMIN_DIRECTORY_GROUP_MEMBER_READONLY, DirectoryScopes.ADMIN_DIRECTORY_USER_READONLY, DirectoryScopes.ADMIN_DIRECTORY_GROUP_READONLY
(note: the hardcoded value of "https://mail.google.com/")
The following string must be added to Manage API client access page in Google Apps:
https://apps-apis.google.com/a/feeds/compliance/audit/,
https://www.googleapis.com/auth/admin.directory.user.readonly, https://mail.google.com/,
https://www.googleapis.com/auth/admin.directory.group.member.readonly, https://www.googleapis.com/auth/admin.directory.group.readonly
I hope this helps someone else!

private GoogleCredential getCredentials(JsonFactory jsonFactory, HttpTransport httpTransport, String impersonateAccount) throws Exception {
Preconditions.checkNotNull(Strings.emptyToNull(impersonateAccount), "Google impersonate account is null");
Preconditions.checkNotNull(Strings.emptyToNull(connection.getServiceAccountId()), "Service Account Email address is null");
Preconditions.checkNotNull(connection.getServiceAccountPrivateKey(), "Service Account Private Key is null");
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountId(connection.getServiceAccountId())
.setServiceAccountScopes(
Arrays.asList(DirectoryScopes.ADMIN_DIRECTORY_USER_READONLY, GmailScopes.MAIL_GOOGLE_COM,
"https://apps-apis.google.com/a/feeds/compliance/audit/",
DirectoryScopes.ADMIN_DIRECTORY_GROUP_MEMBER_READONLY,
DirectoryScopes.ADMIN_DIRECTORY_GROUP_READONLY))
.setServiceAccountUser(impersonateAccount)
.setServiceAccountPrivateKey(connection.getServiceAccountPrivateKey().getPrivateKey())
.build();
setHttpTimeout(credential);
return credential;
}

Related

Creates a default database in Firestore using Google.Apis.Appengine.v1 in C# library

I wanted to create cloud firestore database programmatically using c#, but I am getting error when I run the code. How would I fix permission related issue I am facing? Below is the code and error
private static AppengineService _appEngineService;
public static void IntializeAppEngine() {
GoogleCredential credential = GoogleCredential.GetApplicationDefault();
if (CloudManager.Credential.IsCreateScopedRequired)
{
credential = CloudManager.Credential.CreateScoped(
AppengineService.Scope.CloudPlatform);
}
_appEngineService = new AppengineService(
new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = CloudManager.ApplicationName
});
}
public static void AddCloudFirestore() {
IntializeAppEngine();
var body = new Application {
LocationId = "us-east1",
Id = "projects/" + CloudManager.ProjectId
};
var res = _appEngineService.Apps.Create(body).Execute();
}
Error:
Unhandled exception. The service appengine has thrown an exception.
HttpStatusCode is Forbidden.
Google.Apis.Requests.RequestError
The caller does not have permission [403]
Errors [
Message[The caller does not have permission] Location[ - ] Reason[forbidden] Domain[global]
]
Google.GoogleApiException: The service appengine has thrown an exception. HttpStatusCode is Forbidden. The caller does not have permission
at Google.Apis.Requests.ClientServiceRequest`1.ParseResponse(HttpResponseMessage response)
at Google.Apis.Requests.ClientServiceRequest`1.Execute()
at CloudResourceManager.FirebaseManagement.AddCloudFirestore()
Instead of Appengine, Cloud Firestore REST API can be used as Sarah suggested.
In c# Google.Apis.Firestore.v1 can be used to create cloud firestore.

Enforcing Basic Authentication with RestEasy & TJWS

We use Resteasy to communicate between multiple backend servers & we want to lock this down so not just anyone can attach a client or browser to the restlet server.
We're using Resteasy 3.04 and as our backend services are numerous but very light-weight an embeddded TJWS webserver.
Example Server code:
public class RestEasySSLBasicAuthenticationServer {
static TJWSEmbeddedJaxrsServer webServer;
static class BasicAthenticationSecurityDomain implements SecurityDomain {
#Override
public Principal authenticate(String aUsername, String aPassword) throws SecurityException {
System.out.println("User:" + aUsername + " Password" + aPassword);
if (aPassword.equals("password") == false) {
throw new SecurityException("Access denied to user " + aUsername);
}
return null;
}
#Override
public boolean isUserInRoll(Principal aUsername, String aRole) {
// No role based checks so return true
return true;
}
}
public static void main(String[] args) throws Exception {
// Create embedded TJWS web server
webServer = new TJWSEmbeddedJaxrsServer();
// Set up SSL connections on server
webServer.setSSLPort(8081);
webServer.setSSLKeyStoreFile("K:\\source\\RestEasyTest\\server_localhost.jks");
webServer.setSSLKeyStorePass("krypton");
webServer.setSSLKeyStoreType("JKS");
// Add basic HTTP authentication to the server
webServer.setSecurityDomain( new BasicAthenticationSecurityDomain() );
// Add the restlet resource
webServer.getDeployment().getActualResourceClasses().add(PlayerResource.class);
// Start the web server
webServer.start();
// Run until user presses a key
System.out.print("Web server started. Press a key to stop...");
System.in.read();
// Stop the web server
webServer.stop();
}
}
Example client code:
public class RestEasySSLBasicAuthenticationClient {
public static void main(String[] args) throws Exception {
// Set up the keystore
System.setProperty("javax.net.ssl.keyStore", "K:\\source\\RestEasyTest\\client_localhost.jks");
System.setProperty("javax.net.ssl.keyStoreType", "JKS");
System.setProperty("javax.net.ssl.keyStorePassword", "krypton");
// Create a new Restlet client
Client restletClient = ClientBuilder.newClient();
// *** Even WITHOUT these credentitials we can access the restlet
// restletClient.register(new BasicAuthentication("username", "password"));
// Set up the restlet request target.
WebTarget request = restletClient.target("https://localhost:8081/player/{id}");
request = request.resolveTemplate("id", Long.valueOf(1));
// Build the restlet request
Invocation invocation = request.request("application/xml").buildGet();
// Call the restlet and get returned object
Player result = invocation.invoke( Player.class );
System.out.println(result.toString());
}
}
Using the test client and a registered authentication filter works and as expected I can a 401 access error if I get the password incorrect.
However if no authentication is registered at the client then the server never calls the SecurityDomain check and access is allowed.
How do I enforce a login at the server?
You can ensure all users are authenticated by enabling security on the embedded TJWS web server.
webServer.getDeployment().setSecurityEnabled(true);

Google Plus DomainsAPI wide-domain autorization

Good morning I'm trying to integrate the Google+ Domains API with my company domain but I'm facing some problems.
I'm trying the java approach following the quick start for java but after implement the code the response from the google server is :
Authenticate the domain for hugo.catarino#outsystems.com
Inserting activity
10/Set/2013 17:08:49 com.google.api.client.googleapis.services.AbstractGoogleClient <init>
WARNING: Application name is not set. Call Builder#setApplicationName.
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)
at com.google.plus.samples.quickstart.domains.DomainDelegation.main(DomainDelegation.java:160)
here is used authentication method and my variables:
private static final String SERVICE_ACCOUNT_EMAIL = "638852846577#developer.gserviceaccount.com";
private static final String SERVICE_ACCOUNT_PKCS12_FILE_PATH =
"src/com/google/plus/samples/quickstart/domains/05cab8e819cbd0a747b180c1f22fc93dba916b7b-privatekey.p12";
private static final String USER_EMAIL = "hugo.catarino#outsystems.com";
private static Plus authenticate() throws GeneralSecurityException, IOException {
System.out.println(String.format("Authenticate the domain for %s", USER_EMAIL));
HttpTransport httpTransport = new NetHttpTransport();
JsonFactory jsonFactory = new JacksonFactory();
// Setting the sub field with USER_EMAIL allows you to make API calls using the special keyword
// 'me' in place of a user id for that user.
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
.setServiceAccountScopes(SCOPE)
.setServiceAccountUser(USER_EMAIL)
.setServiceAccountPrivateKeyFromP12File(
new java.io.File(SERVICE_ACCOUNT_PKCS12_FILE_PATH)).build();
// Create and return the Plus service object
Plus service = new Plus.Builder(httpTransport, jsonFactory, credential).build();
return service;
}
My main class has the following code like in the sample:
Plus service = authenticate();
String userId = "me";
String msg = "Happy Monday! #caseofthemondays";
System.out.println("Inserting activity");
// Create the audience of the post
PlusAclentryResource res = new PlusAclentryResource();
// Share to the domain
res.setType("domain");
List<PlusAclentryResource> aclEntries = new ArrayList<PlusAclentryResource>();
aclEntries.add(res);
Acl acl = new Acl();
acl.setItems(aclEntries);
// Required, this does the domain restriction
acl.setDomainRestricted(true);
Activity activity = new Activity()
.setObject(new Activity.PlusObject().setOriginalContent(msg))
.setAccess(acl);
activity = service.activities().insert(userId, activity).execute();
System.out.println(activity);
In domain cPanel the company defined for me the next scopes:
https://www.googleapis.com/auth/plus.circles.read
https://www.googleapis.com/auth/plus.circles.write
https://www.googleapis.com/auth/plus.me
https://www.googleapis.com/auth/plus.media.upload
https://www.googleapis.com/auth/plus.stream.read
https://www.googleapis.com/auth/plus.stream.write
My scope definition is:
private static final List<String> SCOPE = Arrays.asList(
"https://www.googleapis.com/auth/plus.circles.read",
"https://www.googleapis.com/auth/plus.circles.write",
"https://www.googleapis.com/auth/plus.me",
"https://www.googleapis.com/auth/plus.media.upload",
"https://www.googleapis.com/auth/plus.stream.read",
"https://www.googleapis.com/auth/plus.stream.write");
I'm a bit lost here , is there any way of debug this problem or know why is this access denied?
There are several things that you should check.
First, is the private key file that you downloaded from the Google APIs Console in the correct path with your code? This file is referenced by the following variable. This needs to tell the OAuth client library where to find the file.
private static final String SERVICE_ACCOUNT_PKCS12_FILE_PATH =
"/path/to/<public_key_fingerprint>-privatekey.p12";
It is very important that you do not rename the file.
Second, does your scope list in your code match the list of scopes set in the Admin console?
The configuration in the Admin console for your Google Apps domain, and the scopes provided in the request must be identical. Try adjusting the SCOPE variable in your code to be:
private static final List<String> SCOPE = Arrays.asList(
"https://www.googleapis.com/auth/plus.me",
"https://www.googleapis.com/auth/plus.circles.read",
"https://www.googleapis.com/auth/plus.circles.write",
"https://www.googleapis.com/auth/plus.media.upload",
"https://www.googleapis.com/auth/plus.stream.read",
"https://www.googleapis.com/auth/plus.stream.write");
In general, it is best to only request the scopes that you will need, rather than all scopes available.
Third, make sure that the client ID you generated is the one listed on the Admin console entry that specifies the scopes permitted.

Exporting data to Google Spreadsheet from Google App Engine (Java version)

I am getting no where now. When I Google around to find a sample of "Exporting data to Google Spreadsheet from Google App Engine", I see a lot of Google Conversion API, Google Conversion API, Google Spreadsheet API and Google Docs API tutorials. But then they all deprecated from Google when I check at the Google site??? So, what is the most updated right now so I can make use of it?
=====
Okay now I use the Google Drive SDK via OAuth2 to create text file. But I have problem with this:
this is the error:
java.lang.NullPointerException at
java.net.URI$Parser.parse(URI.java:3004) at
java.net.URI.(URI.java:577) at
com.google.api.client.http.GenericUrl.(GenericUrl.java:100) at
com.google.api.client.googleapis.media.MediaHttpUploader.upload(MediaHttpUploader.java:269)
at
com.google.api.services.drive.Drive$Files$Insert.executeUnparsed(Drive.java:309)
at
com.google.api.services.drive.Drive$Files$Insert.execute(Drive.java:331)
at
com.company.dashboard.service.DriveService.initDoc(DriveService.java:84)
this is the code:
private GoogleCredential buildGoogleCredential(Credential credential) {
try {
logger.warning(oauth2Service.getClientCredential().toString());
GoogleCredential googleCredential = new GoogleCredential.Builder()
.setClientSecrets(oauth2Service.getClientCredential())
.setTransport(new NetHttpTransport())
.setJsonFactory(new JacksonFactory()).build();
googleCredential.setAccessToken(credential.getAccessToken());
googleCredential.setRefreshToken(credential.getRefreshToken());
return googleCredential;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private Drive buildService(GoogleCredential credential) {
return new Drive.Builder(new NetHttpTransport(), new JacksonFactory(), credential).build();
}
public void initDoc(HttpServletRequest req)
throws Exception {
User user = UserServiceFactory.getUserService().getCurrentUser();
Credential credential = oauth2Service.getStoredCredential(
user.getUserId(),
(CredentialStore)req.getSession().getServletContext().getAttribute(OAuth2Constant.GOOG_CREDENTIAL_STORE));
if (credential != null) {
logger.warning("Using access token: " + credential.getAccessToken());
try {
GoogleCredential googleCredential = buildGoogleCredential(credential);
Drive service = buildService(googleCredential);
if (service == null) {
logger.warning("very bad!");
}
File body = new File();
body.setTitle("My document");
body.setDescription("A test document");
body.setMimeType("text/plain");
java.io.File fileContent = new java.io.File("document.txt");
FileContent mediaContent = new FileContent("text/plain", fileContent);
service.files().insert(body, mediaContent).execute();
//File file = service.files().insert(body, mediaContent).execute();
//System.out.println("File ID: " + file.getId());
} catch (HttpResponseException e) {
if (e.getStatusCode() == 401) {
logger.warning(e.getMessage());
// Credentials have been revoked.
// TODO: Redirect the user to the authorization URL.
throw new UnsupportedOperationException();
}
} catch (IOException e) {
System.out.println("An error occurred: " + e);
}
}
oauth2Service.getClientCredential() returns (xxx = client id and client secret code, not shown in here)
{"web":{"client_id":"xxx.apps.googleusercontent.com","client_secret":"xxx"}}
This is my scope:
final static List<String> SCOPES = Arrays.asList("https://www.googleapis.com/auth/userinfo.profile",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/docs",
"https://www.googleapis.com/auth/drive.file");
final static String AUTH_RESOURCE_LOC = "/client_secrets.json";
final static String OATH_CALLBACK = "http://localhost:8888/oauth2callback";
This line
service.files().insert(body, mediaContent).execute();
throws NullPointerException. Any idea what had gone wrong???
P/S: Credential = com.google.api.client.auth.oauth2.Credential. I have my OAuth2 everything works good. I can retrieve user info without problem but not the Drive API. service is not null as you see I put it the log as "very bad" and it is not shown. No 401 exception is thrown means my Oauth2 is good with the scopes.
=======
DARN!!!! Finally solved the issue!!! My code was totally correct! Just I enabled the wrong API! It should be Drive API instead of Drive SDK API :/
You can generate a csv file from GAE and upload it using the Drive API with ?convert=true to have it automatically converted to a Google spreadsheet:
https://developers.google.com/drive/v2/reference/files/insert

email address on GAE

I have an application on google app engine like abc.appspot.com can I have an email address to send/receive emails like admin#abc.appspot.com kindly help me.
Edit
here is my SendMail class
public class SendMail {
private static String fromAddress = "abc#gmail.com";
private static Logger log = Logger.getLogger(SendMail.class.getCanonicalName());
// Send the Mail
public void send(String toAddress, String subject, String msgBody)
throws IOException {
Properties props = new Properties();
Session session = Session.getDefaultInstance(props, null);
try {
Message msg = new MimeMessage(session);
msg.setFrom(new InternetAddress(fromAddress));
InternetAddress to = new InternetAddress(toAddress);
msg.addRecipient(Message.RecipientType.TO, to);
msg.setSubject(subject);
msg.setText(msgBody);
Transport.send(msg, new InternetAddress[] { to });
} catch (AddressException addressException) {
log.log(Level.SEVERE, "Address Exception , mail could not be sent", addressException);
} catch (MessagingException messageException) {
log.log(Level.SEVERE, "Messaging Exception , mail could not be sent", messageException);
}
}
}
So it sends an email regarding abc#gmail.com but I want that it should send from email#abc.appspot.com.
You can only receive emails in the form of #abc.appspotmail.com. AFAIK there is no way to have #abc.appspot.com as receiving address.
If you wan to receive emails from your custom domain, e.g. #abc.com, than the only way is to have external email service forward emails to your #abc.appspotmail.com. Most domain registrars offer free limited email service with forwarding (we use GoDaddy and get limited forwarding free).
Yes you can: https://developers.google.com/appengine/docs/java/mail/usingjavamail#Senders_and_Recipients

Resources