EWS Item.Copy causes ErrorAccessDenied - exchangewebservices

I want to copy items between mailboxes used EWS managed API. Here I've met strange situation.
When I try first to get destination folder and then copy item using its ID I get an error ErrorAccessDenied.
this.exchangeService.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, "test1#test.local");
var folder = Folder.Bind(this.exchangeService, WellKnownFolderName.Inbox);
item.Copy(folder.Id);
This gets an error
If I create a FolderId object instance specifying well known folder name (Inbox) and mailbox name I get no problems.
var folderId = new FolderId(WellKnownFolderName.Inbox, new Mailbox("test1#test.local"));
item.Copy(folderId);
This works
Is such behavior by design? Or I can use destination folder not just well known one?

I believe this behavior is by design. In the first example, the calling account (acct1) is impersonating acct2. I think that the request is being processed as acct2 trying to bind to the inbox of acct1 since the credentials of the call are associated with acct1. The context for calls are based on the identifiers which contain the SMTP address of the target mailbox.
Your second example explicitly identifies the mailbox that should be targeted. All subsequent identifiers accessed based on that folder identifier will have the context of test1#test.local.
I think you can change your first call to this to make it work:
var folder = Folder.Bind(this.exchangeService, new FolderId(WellKnownFolderName.Inbox, new Mailbox("test1#test.local")));
I know you are copying to a folder in to test1#test.local mailbox. Whose mailbox contains 'item'? I can't recall if it works to use impersonation to copy across mailboxes. I'm interested to know how it works for you.

Related

Using addEditor(s) with users outside your workspace

I used appscript to create copies of a document and share them with a particular user, this works fine in that the code creates the document and the user receives an invitation to edit, but I have found that at least some users are not able to edit the document. When I view the sharing options on the individual docs, I can see that they are restricted to my workspace only.
I had the same issue when doing this process manually, but thought addEditor would be able to override this, as the shared Gdrive is not restricted in this way overall (i.e, we can share docs with individuals by adding them as editor, from outside our organisation.) . I do not want to have to individually share >100 docs per week manually, so any fixes/workarounds would be very appreciated.
Some limitations: the doc must be shared to one user exactly, but still be accessible to everyone in my workspace.
All of the users I am sharing the document are external to the organisation.
I tried making the master copy of the file accessible to anyone who had the link, hoping that this would mean they could access the file without manually approving the request, but it looks like this doesn't transfer to the copies. I'm not sure enough of the actual issue to know that this would solve it.
This is how the code looks:
function createCopy() {
let file = DriveApp.getFileById("ID of file to copy");
let sheet = SpreadsheetApp.openByUrl('Sheet with names and emails').getSheetByName("Sheet1");
let range = sheet.getRange('D2:D5');
let email = sheet.getRange('C2:C5').getValues().flat();
let values = range.getValues();
let folder = DriveApp.getFolderById("Destination Folder");
for (let i = 0; i < values.length; i++) {
Logger.log(values[i]);
file.makeCopy(values[i].toString(), folder).addEditor(email[i]);
}
}
From the question
Some limitations: the doc must be shared to one user exactly, but still be accessible to everyone in my workspace.
Try this
file.makeCopy(values[i].toString(), folder).addEditor(email[i]);
by
file.makeCopy(values[i].toString(), folder)
.addEditor(email[i])
.setSharing(DriveApp.Access.DOMAIN, DriveApp.Permission.EDIT)
References
https://developers.google.com/apps-script/reference/drive/file#setsharingaccesstype,-permissiontype

How to properly set sharing permissions on a new file in a GAS-based form

I have had some inconsistent results using GAS to set sharing for files created in the script, from an uploaded blob.
Gobally, the GAS script serves an Html form, collects the uploaded file, and makes some logging and processing. It is a heavily modified version of this https://ctrlq.org/code/19747-google-forms-upload-files adapted for report submissions from students.
The only clues I have so far are related to the position of the line that sets the sharing, relative to the lines in which the file is created:
var folder = DriveApp.getFolderById(dropbox);
// Get the blob
var contentType = data.substring(5,data.indexOf(';'))
var bytes = Utilities.base64Decode(data.substr(data.indexOf('base64,')+7))
var blob = Utilities.newBlob(bytes, contentType, filename)
// Create a folder for the file if it does not exist
try{
var subFolder = folder.getFolder(tp);
}
catch(e) {
var subFolder = folder.createFolder(tp);
}
var file = subFolder.createFile(blob) // Create the file
From this point on, strange things happen. I have narrowed down the problem to the actual line that sets the sharing:
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW)
If this is immediately after the file creation, it works only some times. Over 200 reports have been submitted, and ~50% of them are visible to anyone with the link, while the rest are only shared privately.
Whether these permissions are set or not, everything else completes successfully. So i have no error log to know for sure what is going on.
By moving this line to the end of the function, subsequent submissions have properly set the permissions:
// .... processing lines that include logging to a spreadsheet, sending emails, and setting other permissions.
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW)
return "OK";
My guess is that, for some reason, the time between file creation and permission modification affects the effectivenes of the .setSharing() line.
One hypothesis is that the file may be inheriting the parent folder's permissions (which is not public) with "lag". In other words, the permissions set by the .setSharing() function would be overwritten by some obsecure inheritance function behind file creation line:
var file = subFolder.createFile(blob) // Create the file
The "default" permissions must surely be set at some point, but there is no "flush" function, that I know of, to force or wait for this to happen (such as the one used in Google Spreadsheets).
EDIT: i have found a thread where similar "non determinism" occurs, involving permission inheritance from the parent folder (see this issue).
I would like to know how to make sure that permissions are always set correctly.
Cheers
:)

Google Drive Folders/Files Created Using API Not Visible on Google Interface

This is rather strange. I used Google Drive API to create a folder in Google Drive and then uploaded a file there. I can retrieve the folder and file using the same API (the code is working fine in all respect). However, when I go to Google Drive Web interface, I can't seem to find the folder or file. The file also doesn't sync to my local drive. Is there a setting in API or elsewhere to set the "visibility" ON?
Thank you in advance.
I had the same issue. Turned out to be permissions. When the file is uploaded by the service account, the service account is set as the owner, and then you can't see the files from the Drive UI. I found the solution online (but can't seem to find it again...)
This is what I did...
It's C#, your question didn't specify. The code you're interested in is the permission stuff after you get the response body after the upload...
FilesResource.InsertMediaUpload request = service.Files.Insert(body, stream, "text/plain");
request.Upload();
//Start here...
Google.Apis.Drive.v2.Data.File file = request.ResponseBody;
Permission newPermission = new Permission();
newPermission.Value = "yourdriveaccount#domain.com";
newPermission.Type = "user";
newPermission.Role = "reader";
service.Permissions.Insert(newPermission, file.Id).Execute();
The file was visible on the Drive UI after this. I tried specifying "owner" for the role, like the api suggests, but I got and error saying that they're working on it. I haven't played around with the other setting yet, (I literary did this last night). Let me know if you have any luck with any other combinations on permissions.
Hope that helps
I had the same issue, but this got solved my using a list data type for parents parameter, eg: If one wants to create a folder under a folder("1TBymLMZXPGkouw-lTQ0EccN0CMb_yxUB") then the python code would look something like
drive_service = build('drive', 'v3', credentials=creds)
body={
'name':'generated_folder',
'parents':['1TBymLMZXPGkouw-lTQ0EccN0CMb_yxUB'],
'mimeType':'application/vnd.google-apps.folder'
}
doc = drive_service.files().create(body=body).execute()
While permission issue is the main cause of this problem. What I did to make the folders or files appear after I uploaded it with service account was to specify the parent folder. If you upload / create folder / files without parent folder ID, that object's owner will be the service account that you are using.
By specifying parent ID, it will use the inherited permissions.
Here's the code I use in php (google/apiclient)
$driveFile = new Google\Service\Drive\DriveFile();
$driveFile->name = $req->name;
$driveFile->mimeType = 'application/vnd.google-apps.folder';
$driveFile->parents = ['17SqMne7a27sKVviHcwPn87epV7vOwLko'];
$result = $service->files->create($driveFile);
When you create the folder, you should ensure you set a parent, such as 'root'. Without this, it will be not appear in 'My Drive' and only in Search (Have you tried searching in the UI?)
Since you have already created the folder, you can update the file and give it the parent root as well.
You can test it out using the Parents insert 'Try it now' example.
Put your Folders ID in the fileId box, then in the request body, add root in the ID field.
private void SetFilePermission(string fileId)
{
Permission adminPermission = new Permission
{
EmailAddress = "test#gmail.com", // email address of drive where
//you want to see files
Type = "user",
Role = "owner"
};
var permissionRequest = _driveService.Permissions.Create(adminPermission, fileId);
permissionRequest.TransferOwnership = true; // to make owner (important)
permissionRequest.Execute();
Permission globalPermission = new Permission
{
Type = "anyone",
Role = "reader"
};
var globalpermissionRequest = _driveService.Permissions.Create(globalPermission, fileId);
globalpermissionRequest.Execute();
}

Google Drive/OAuth - Can't figure out how to get re-usable GoogleCredentials

I've successfully installed and run the Google Drive Quick Start application called DriveCommandLine. I've also adapted it a little to GET file info for one of the files in my Drive account.
What I would like to do now is save the credentials somehow and re-use them without the user having to visit a web page each time to get an authorization code. I have checked out this page with instructions to Retrieve and Use OAuth 2.0 credentials. In order to use the example class (MyClass), I have modified the line in DriveCommandLine where the Credential object is instantiated:
Credential credential = MyClass.getCredentials(code, "");
This results in the following exception being thrown:
java.lang.NullPointerException
at com.google.common.base.Preconditions.checkNotNull(Preconditions.java:187)
at com.google.api.client.json.jackson.JacksonFactory.createJsonParser(JacksonFactory.java:84)
at com.google.api.client.json.JsonFactory.fromInputStream(JsonFactory.java:247)
at com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets.load(GoogleClientSecrets.java:168)
at googledrive.MyClass.getFlow(MyClass.java:145)
at googledrive.MyClass.exchangeCode(MyClass.java:166)
at googledrive.MyClass.getCredentials(MyClass.java:239)
at googledrive.DriveCommandLine.<init>(DriveCommandLine.java:56)
at googledrive.DriveCommandLine.main(DriveCommandLine.java:115)
I've been looking at these APIs (Google Drive and OAuth) for 2 days now and have made very little progress. I'd really appreciate some help with the above error and the problem of getting persistent credentials in general.
This whole structure seems unnecessarily complicated to me. Anybody care to explain why I can't just create a simple Credential object by passing in my Google username and password?
Thanks,
Brian O Carroll, Dublin, Ireland
* Update *
Ok, I've just gotten around the above error and now I have a new one.
The way I got around the first problem was by modifying MyClass.getFlow(). Instead of creating a GoogleClientServices object from a json file, I have used a different version of GoogleAuthorizationCodeFlow.Builder that allows you to enter the client ID and client secret directly as Strings:
flow = new GoogleAuthorizationCodeFlow.Builder(httpTransport, jsonFactory, "<MY CLIENT ID>", "<MY CLIENT SECRET>", SCOPES).setAccessType("offline").setApprovalPrompt("force").build();
The problem I have now is that I get the following error when I try to use flow (GoogleAuthorizationCodeFlow object) to exchange the authorization code for the Credentials object:
An error occurred: com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad Request
{
"error" : "invalid_scope"
}
googledrive.MyClass$CodeExchangeException
at googledrive.MyClass.exchangeCode(MyClass.java:185)
at googledrive.MyClass.getCredentials(MyClass.java:262)
at googledrive.DriveCommandLine.<init>(DriveCommandLine.java:56)
at googledrive.DriveCommandLine.main(DriveCommandLine.java:115)
Is there some other scope I should be using for this? I am currently using the array of scopes provided with MyClass:
private static final List<String> SCOPES = Arrays.asList(
"https://www.googleapis.com/auth/drive.file",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile");
Thanks!
I feel your pain. I'm two months in and still getting surprised.
Some of my learnings...
When you request user permissions, specify "offline=true". This will ("sometimes" sic) return a refreshtoken, which is as good as a password with restricted permissions. You can store this and reuse it at any time (until the user revokes it) to fetch an access token.
My feeling is that the Google SDKs are more of a hinderence than a help. One by one, I've stopped using them and now call the REST API directly.
On your last point, you can (just) use the Google clientlogin protocol to access the previous generation of APIs. However this is totally deprecated and will shortly be turned off. OAuth is designed to give fine grained control of authorisation which is intrinsically complex. So although I agree it's complicated, I don't think it's unnecessarily so. We live in a complicated world :-)
Your and mine experiences show that the development community is still in need of a consolidated document and recipes to get this stuff into our rear-view mirrors so we can focus on the task at hand.
Oath2Scopes is imported as follows:
import com.google.api.services.oauth2.Oauth2Scopes;
You need to have the jar file 'google-api-services-oauth2-v2-rev15-1.8.0-beta.jar' in your class path to access that package. It can be downloaded here.
No, I don't know how to get Credentials without having to visit the authorization URL at least once and copy the code. I've modified MyClass to store and retrieve credentials from a database (in my case, it's a simple table that contains userid, accesstoken and refreshtoken). This way I only have to get the authorization code once and once I get the access/refresh tokens, I can reuse them to make a GoogleCredential object. Here's how Imake the GoogleCredential object:
GoogleCredential credential = new GoogleCredential.Builder().setJsonFactory(jsonFactory)
.setTransport(httpTransport).setClientSecrets(clientid, clientsecret).build();
credential.setAccessToken(accessToken);
credential.setRefreshToken(refreshToken);
Just enter your clientid, clientsecret, accessToken and refreshToken above.
I don't really have a whole lot of time to separate and tidy up my entire code to post it up here but if you're still having problems, let me know and I'll see what I can do. Although, you are effectively asking a blind man for directions. My understanding of this whole system is very sketchy!
Cheers,
Brian
Ok, I've finally solved the second problem above and I'm finally getting a working GoogleCredential object with an access token and a refresh token.
I kept trying to solve the scopes problem by modifying the list of scopes in MyClass (the one that manages credentials). In the end I needed to adjust the scopes in my modified version of DriveCommandLine (the one that's originally used to get an authorization code). I added 2 scopes from Oauth2Scopes:
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
httpTransport, jsonFactory, CLIENT_ID, CLIENT_SECRET,
Arrays.asList(DriveScopes.DRIVE, Oauth2Scopes.USERINFO_EMAIL, Oauth2Scopes.USERINFO_PROFILE))
.setAccessType("offline").setApprovalPrompt("force").build();
Adding the scopes for user information allowed me to get the userid later in MyClass. I can now use the userid to store the credentials in a database for re-use (without having to get the user to go to a URL each time). I also set the access type to "offline" as suggested by pinoyyid.

Determine if Outlook Contact has been deleted using EWS 2007

i am able to retrieve lists of contacts for specified mailboxes using exchange web services. my issue is that some of the contacts returned have been deleted by the outlook user, and i need to determine which ones. how can i do this?
all the examples i've seen online use this method, but never for contacts.
i have tried setting the Traversal property of the ItemView variable to SoftDeleted, but that does not return anything.
below is the pertinent portion of my code:
ItemView itemViewDeleted = new ItemView(100);
itemViewDeleted.Traversal = ItemTraversal.SoftDeleted;
FindItemsResults<Item> deletedItems = svc.FindItems(WellKnownFolderName.Contacts, itemViewDeleted);
You need to check the WellKnownFolderName.DeletedItems folder. That is where my contacts go when I delete them.
There are (3) ways to delete a Contact. See TechNet for Exchange terminology reference.
Delete (moved to Deleted Items folder - WellKnownFolderName.DeletedItems)
Soft Delete (moved to Recoverable Items folder - WellKnownFolderName.RecoverableItemsDeletions)
Hard Delete (purged from mailbox - WellKnownFolderName.RecoverableItemsPurges)