7

I am attempting to transfer ownership from a Service Account created document to another user who resides within the same Google Apps account using the code below but am getting the following error

The resource body includes fields which are not directly writable. [403] Errors [Message[The resource body includes fields which are not directly writable.] Location[ - ] Reason[fieldNotWritable] Domain[global]]

        var service = GetService();

        try
        {
            var permission = GetPermission(fileId, email);
            permission.Role = "owner";

            var updatePermission = service.Permissions.Update(permission, fileId, permission.Id);
            updatePermission.TransferOwnership = true;
            return updatePermission.Execute();
        }
        catch (Exception e)
        {
            Console.WriteLine("An error occurred: " + e.Message);
        }
        return null;

Commenting out // permission.Role = "owner"; returns the error below

The transferOwnership parameter must be enabled when the permission role is 'owner'. [403] Errors [Message[The transferOwnership parameter must be enabled when the permission role is 'owner'.] Location[transferOwnership - parameter] Reason[forbidden] Domain[global]]

Assigning any other permissions works fine. Therefore, is this a limitation of the Service Account not being able to transfer ownership to any other account that doesn't use the @gserviceaccount.com email address (i.e. our-project@appspot.gserviceaccount.com > email@domain.com)?

The email@domain.com email address has been created and is managed within Google Apps.

In the case, it is not achievable, any pointers on where to look next? We need multiple users to have the ability to create documents ad hoc and assign permissions and transfer ownership on the fly via the API.

Thanks

timkly
  • 793
  • 6
  • 14
  • Try patch instead of update? https://developers.google.com/drive/v2/reference/permissions/patch – Linda Lawton - DaImTo Aug 12 '16 at 06:26
  • Thanks @DalmTo but I'm using V3 API and the Update function is the old Patch function from V2 [Migrate to Google Drive API v3](https://developers.google.com/drive/v3/web/migration). – timkly Aug 12 '16 at 06:37
  • Ok was a guess it was hard to tell if it was v2 or v3 with your code. Patching worked in V2. Permissions are a pain with Google drive. – Linda Lawton - DaImTo Aug 12 '16 at 06:43
  • Like you have said, you can use ServiceAccountCredential.Initializer method to [impersonate a user](https://support.google.com/a/answer/1247799?hl=en) that has a Drive service privilege and the Data Transfer privilege. This in turn will delegate a domain-wide access to the service account. See [this](http://stackoverflow.com/a/25304587/5995040) code implementation. Hope this helps! – Mr.Rebot Aug 13 '16 at 06:56

3 Answers3

5

I have found the answer and am posting for anyone else who comes across this question.

  1. You can not use the 'Service Account Key JSON file' as recommended by Google.
  2. You need to use the p.12 certificate file for authentication.
  3. The code to create a drive service for mimicking accounts is as follows.

    public DriveService GetService(string certificatePath, string certificatePassword, string googleAppsEmailAccount, string emailAccountToMimic, bool allowWrite = true)
    {
        var certificate = new X509Certificate2(certificatePath, certificatePassword, X509KeyStorageFlags.Exportable);
    
        var credential = new ServiceAccountCredential(
           new ServiceAccountCredential.Initializer(googleAppsEmailAccount)
           {
               Scopes = new[] { allowWrite ? DriveService.Scope.Drive : DriveService.Scope.DriveReadonly },
               User = emailAccountToMimic
           }.FromCertificate(certificate));
    
        // Create the service.
        return new DriveService(new BaseClientService.Initializer
        {
            HttpClientInitializer = credential,
            ApplicationName = ApplicationName
        });
    }
    
  4. You need to follow the steps listed here to delegate domain-wide authority to the service account.

  5. Allow 5 to 10 minutes after completing step 4.
  6. You can now create documents under the 'emailAccountToMimic' user which sets them to be the owner during creation.
timkly
  • 793
  • 6
  • 14
  • FYI - Turns out you need to create the document using the emailAccountToMimic account and then assign 'Writer' permissions to the document for the Service Account who can then write to the document. – timkly Aug 15 '16 at 07:17
1

I don't think it is possible to transfer the ownership from a non-ServiceAccount to a ServiceAccount, vice versa.

If you do that interactively, you will get the below error:

enter image description here

Typically, the document can be created and owned by the users and ownership transfer can be done using their own credentials. You will also have the option to impersonate as the owner if your Service Account is granted with the domain-wide delegation correctly.

some1
  • 857
  • 5
  • 11
  • Thanks @some1. I'm actually trying to assign permissions from a ServiceAccount to a non-ServiceAccount and I've set up domain wide delegation for the service account and assigned permissions as explained [here](https://developers.google.com/identity/protocols/OAuth2ServiceAccount) but I'm using the Service Account Key JSON file (as recommend by Google) which has a different initialiser to OAuth 2.0 authentication which relies on a certificate instead... but it also allows for account impersonation when using the ServiceAccountCredential.Initializer method. – timkly Aug 12 '16 at 07:03
0

You can actually transfer ownership of a file or folder from a Service Account's Google Drive to a personal account through the Google Drive API

You must first make a "transfer ownership" request from an personal account (the owner) . This request can be made from the owner's Google Drive UI screen. The message then states that the recipient must accept the "transfer ownership" invitation. But Service Accounts don't have Gmail's Inbox or UI screens to do that. You can now write a block of code using the Google Drive API for accepting it.

Here is sample Python code

def transfer_ownership(folder_id, SA_email):
        # Define the permission body
        permission = {
            'type': 'user',
            'role': 'owner',
            'emailAddress': SA_email,
        }

        try:
            service.permissions().create(
                fileId = folder_id,
                body = permission,
                transferOwnership = True,
            ).execute()
            print(
                "Ownership of the resource has been transferred to the new Service Account {}".format(
                    SA_email
                )
            )
        except Exception as e:
            err_msg = "An error occurred while transferring the ownership {}".format(str(e))
            raise RuntimeError(err_msg)