Upload Files to Google Drive Using Service Account C# - google-drive-api

I am building feature to upload files from my web application to my clients google drive. I don't want application to authenticate the user. Who ever is uploading any file from my file i want that to be uploaded to my client's google drive.
I am assuming that Service Account is the One which is used for this purpose. am I correct ?
When i upload the files using following code it gets uploaded to successfully but they are not visible under "My Drive". Then i added a permission which basically shares that file with my gmail account. and then it is visible under "Shared With Me" section.
I want those files to be available under "My Drive" section just like normal files.
Can any one point out what i am doing wrong here ?
private DriveService GetServiceInstance()
{
try
{
var certificate = new X509Certificate2(Server.MapPath("path to .p12 key"), "notasecret", X509KeyStorageFlags.Exportable);
string[] scopes = new string[] {
DriveService.Scope.Drive,
DriveService.Scope.DriveFile,
DriveService.Scope.DriveAppdata,
DriveService.Scope.DriveMetadata
};
var credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer("Client Email")
{
Scopes = scopes
}.FromCertificate(certificate));
// Create the service.
var service = new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Drive API Sample",
});
return service;
}
catch (Exception ex)
{
throw;
}
}
protected void btnSave_Click(object sender, EventArgs e)
{
try
{
if (fu.HasFile)
{
DriveService service = GetServiceInstance();
Google.Apis.Drive.v2.Data.File body = new Google.Apis.Drive.v2.Data.File();
body.Title = fu.FileName;
body.MimeType = fu.PostedFile.ContentType;
FilesResource.InsertMediaUpload request = service.Files.Insert(body, fu.PostedFile.InputStream, fu.PostedFile.ContentType);
request.Upload();
// I want to eliminate this part. where i do not have to give permission. As i want these files to get stored on same google drive account for which i have generated this service account and client keys
Google.Apis.Drive.v2.Data.File file = request.ResponseBody;
Google.Apis.Drive.v2.Data.Permission newPermission = new Google.Apis.Drive.v2.Data.Permission();
newPermission.Value = "my email address";
newPermission.Type = "user";
newPermission.Role = "writer";
Google.Apis.Drive.v2.PermissionsResource.InsertRequest insertRequest = service.Permissions.Insert(newPermission, file.Id);
insertRequest.SendNotificationEmails = false;
insertRequest.Execute();
}
}
catch (Exception ex)
{
throw;
}
}

Don't use a Service Account. Embed a refresh token for the target account in your server. See How do I authorise an app (web or installed) without user intervention? (canonical ?)

I have created a specific folder and shared the folder to my email address. Uploaded the document to that folder. This resolved my issue.

Related

How to set up google api in asp.net mvc to access docs

About 6 months ago I set up a web application in the google developers console so that employees of our internal web site could initiate emails which would read a template doc in a google account, merge some fields and then download a pdf version of it to email out.
Now we have to move those template docs to a different google managed domain/user account so I've made copies of the documents in the new account and updated our references with the new doc ids.
In addition, the email I had when I originally created this application in the google dev console is going away as of the first of the year. So I also have to recreate the app under a new account.
I've done that and matched all the settings of the original app. However, when I try to access a document I get the error Google.Apis.Auth.OAuth2.Responses.TokenResponseException: 'Error:"unauthorized_client", Description:"Unauthorized", Uri:""'
I had followed this page in setting up the original user authentication. I know there was a ton of trial and error before I actually got it working and I must be forgetting something. I'm wondering if it's tied to needing to reauthenticate the new app. Although I'm specifying the new clientid and clientsecret from the new app, I don't get the popup asking me to give permission to the app. I would expect with the new credential info that it would open that window asking me to give permission. Here's that file for reference. Any ideas?
public class AuthCallbackController : Google.Apis.Auth.OAuth2.Mvc.Controllers.AuthCallbackController
{
protected override FlowMetadata FlowData => new AppFlowMetadata();
}
public class AppFlowMetadata : FlowMetadata
{
private static readonly IAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = AwsSecrets.GoogleCreds.ClientId,
ClientSecret = AwsSecrets.GoogleCreds.ClientSecret
},
Scopes = new[] {DriveService.Scope.Drive},
DataStore = new FileDataStore("Drive.Api.Auth.Store")
});
public override IAuthorizationCodeFlow Flow => flow;
public override string GetUserId(Controller controller)
{
return "userid";
}
}
public class GoogleController : TECWareControllerBase
{
private readonly IGoogleCredentialService _gservice;
public GoogleController(IGoogleCredentialService gservice)
{
_gservice = gservice;
}
public async Task<ActionResult> IndexAsync(CancellationToken cancellationToken)
{
var result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).AuthorizeAsync(cancellationToken);
if (result.Credential != null)
{
_gservice.SaveRefreshToken(result.Credential.Token.RefreshToken);
return View();
}
return new RedirectResult(result.RedirectUri);
}
}
I finally found a way to get this working.
First off in this method
private static readonly IAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = AwsSecrets.GoogleCreds.ClientId,
ClientSecret = AwsSecrets.GoogleCreds.ClientSecret
},
Scopes = new[] {DriveService.Scope.Drive},
DataStore = new FileDataStore("Drive.Api.Auth.Store")
});
I had to change the FileDataStore("Drive.Api.Auth.Store") key to something else like FileDataStore("GoogleAuth")
That forced the authentication to fire up.
Unfortunately, google then complained about an invalid redirect uri. The following code returned a redirect uri of http://localhost:11224/AuthCallback/IndexAsync which didn't even exist in my web application's Authorized redirect uris. It should have been http://localhost:11224/MVC/AuthCallback/IndexAsync. So in the url result's redirect url I changed it to what it should have been which allowed me to complete the authorization. Now I can access the documents in the authenticated account.
var result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).AuthorizeAsync(cancellationToken);

Cannot find files that I uploaded to google drive using google drive sdk

I am using .NET SDK for google drive.
I have successfully managed to upload a file on google drive but when I log in using browser cannot see the file there?
Here is my code:
private string privKey = "-----BEGIN PRIVATE KEY-----\MYKEYHERE-----END PRIVATE KEY-----\n";
private string[] scopes = { DriveService.Scope.Drive };
string serviceAccountEmail = "<myaccounthere>";
public File uploadToDrive(string uploadFilePath)
{
ServiceAccountCredential credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = scopes
}.FromPrivateKey(privKey));
BaseClientService.Initializer initializer = new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
};
// Create Drive API service.
var service = new DriveService(initializer);
if (!string.IsNullOrEmpty(uploadFilePath))
{
File fileMetadata = new File();
fileMetadata.Name = System.IO.Path.GetFileName(uploadFilePath);
fileMetadata.MimeType = GetMimeType(uploadFilePath);
try
{
System.IO.FileStream uploadStream = new System.IO.FileStream(uploadFilePath, System.IO.FileMode.Open, System.IO.FileAccess.Read);
FilesResource.CreateMediaUpload request = service.Files.Create(fileMetadata, uploadStream, GetMimeType(uploadFilePath));
request.ProgressChanged += Upload_ProgressChanged;
request.ResponseReceived += UploadRequest_ResponseReceived;
var task = request.UploadAsync();
task.ContinueWith(t =>
{
});
return request.ResponseBody;
}
catch (System.IO.IOException iox)
{
return null;
}
catch (Exception e)
{
//Log
return null;
}
}
else
{
//Log file does not exist
return null;
}
}
private string GetMimeType(string fileName)
{
string mimeType = "application/unknown";
string ext = System.IO.Path.GetExtension(fileName).ToLower();
Microsoft.Win32.RegistryKey regKey = Microsoft.Win32.Registry.ClassesRoot.OpenSubKey(ext);
if (regKey != null && regKey.GetValue("Content Type") != null)
mimeType = regKey.GetValue("Content Type").ToString();
return mimeType;
}
private void Upload_ProgressChanged(IUploadProgress progress)
{
Debug.WriteLine(progress.Status + " " + progress.BytesSent);
}
private void UploadRequest_ResponseReceived(Google.Apis.Drive.v3.Data.File file)
{
Debug.WriteLine(file.Name + " was uploaded successfully");
}
Does anyone happen to know what am I doing wrong?
Thanks in advance
If that doesn't work, you can try the suggested answer in this related SO question:
Since you are using a Service Account, all the folders and files will be created in this Service Account's Drive which cannot be accessed through a web UI and will be limited to the default quota.
To add content in a user's Drive, you will need to go through the regular OAuth 2.0 flow to retrieve credentials from this user. You can find more information about OAuth 2.0 on this pages:
Retrieve and use OAuth 2.0 credentials.
Quickstart: it has a quickstart sample in C# that you could use.
Using OAuth 2.0 to access Google APIs
Hope this helps!
Yes you are right. Service accounts have this limitation. For anyone that has the same problem what I did (using .NET) to overcome this was to enable Domain-Wide delegation and impersonate the account that I needed.
e.g my service account is srv1#myaccount.gserviceaccount.com impersonates srv1#gmail.com
ServiceAccountCredential credential = new ServiceAccountCredential(new ServiceAccountCredential.Initializer("srv1#myaccount.gserviceaccount.com")
{
Scopes = new string[] { DriveService.Scope.Drive },
User = "srv1#gmail.com"
}.FromPrivateKey(PRIVATE_KEY));
When I login to google drive using srv1#gmail.com I can now see the files

Google Drive REST API (Server to Server): no binding with account files

I try getting list of Google Drive files from my application (simple Google account)(https://developers.google.com/identity/protocols/OAuth2ServiceAccount)
List with OAuth2 authorization work perfect: https://developers.google.com/drive/v3/reference/files/list#try-it
I've create service account with delegation to Google Apps domain (https://console.developers.google.com/iam-admin/serviceaccounts):
and download json service key.
Apply to GD from application:
private static HttpTransport HTTP_TRANSPORT;
private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
static {
try {
HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport();
} catch (Throwable t) {
t.printStackTrace();
System.exit(1);
}
}
public static void main(String[] args) throws IOException {
Credential credential = GoogleCredential
.fromStream(new FileInputStream(".\\config\\google_drive_api.json"))
.createScoped(DriveScopes.all());
Drive service = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential).setApplicationName("javawebinar-1314").build();
FileList result = service.files().list().execute();
List<File> files = result.getFiles();
System.out.println(files.size());
API work: result at the beginning was single "Getting started" file, now result is 0. It seems that service account has no any relation to my own and delegating to doman has no meening for me as I have no domain account.
Are there any possibility to manage my simple google account google drive files by REST API ?

Google Drive API authentication without user interaction

I'm looking for a Google Drive API authentication scheme that will give a service application (on a server) rights to create shared documents in a Drive folder, without user interaction.
Google's current unique name for the specific authentication scheme I should use for this is probably a sufficient answer to this question.
Although the document creation will occur in response to a user action, the documents will not be permanently associated with those users and I do not wish to require any user to present a Google account. Instead, I wish the user to be able to access the document via an "Anyone with the link can edit"-type URL displayed on a web page after the document is created.
This is intended to automatically generate documents for multiple generally anonymous people to collaborate, and all documents will be stored in a single folder.
There's a good chance this is a duplicate of this question: Google Drive API username + password authentication. Unfortunately, the accepted answer doesn't contain enough information for me to find my way now that the links it references are dead.
It may also be a duplicate of other questions that have accepted but unclear answers, such as: .NET Google Docs API Authentication (without user interaction), How do I authenticate Google Calendar API v3 without user interaction?, and Drive API doc upload from a server without user interaction.
Authenticating as a service account was the approach I needed.
The Google SDK actions were simply misleading. When I provided some incorrect values it fell back to user-based authentication (automatically opening a web browser to request interactive credentials). I incorrectly interpreted this to mean that the service account functionality was implemented as a long-term key approved by and in the context of a specific interactive user, or something similar.
No user interaction was necessary, however the .p12 certificate was required, rather than whatever credentials the default .json file provided (which I had tried using in a number of ways). Here's the code I used:
using Google.Apis.Auth.OAuth2;
using Google.Apis.Drive.v2;
using Google.Apis.Drive.v2.Data;
using Google.Apis.Http;
using Google.Apis.Services;
using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using GData = Google.Apis.Drive.v2.Data;
public class Drive
{
private const string GoogleDocMimeType = "application/vnd.google-apps.document";
/// <summary>
/// Creates a drive service, authenticated using information found in the Google Developers Console under "APIs & auth / Credentials / OAuth / Service account"
/// </summary>
/// <param name="svcEmail">The service account "Email address"</param>
/// <param name="certPath">The path to the service account "P12 key" file</param>
public Drive(string svcEmail, string certPath)
{
Service = AuthenticateService(svcEmail, certPath);
}
private DriveService Service
{
get;
set;
}
/// <summary>
/// Creates a "Google Doc" and shares it with anyone with the link
/// </summary>
/// <param name="title"></param>
/// <returns>The drive FileId, accessible at https://docs.google.com/document/d/FileId </returns>
public async Task<string> CreateShared(string title)
{
var fileId = await CreateDocument(title);
await ShareFile(fileId);
return fileId;
}
private async Task<string> CreateDocument(string title)
{
var file = new GData.File
{
Title = title,
MimeType = GoogleDocMimeType
};
file = await Service.Files.Insert(file).ExecuteAsync();
return file.Id;
}
private async Task ShareFile(string fileId)
{
Permission permission = new Permission
{
Type = "anyone",
Role = "writer",
WithLink = true
};
var a = Service.Permissions.Insert(permission, fileId);
await a.ExecuteAsync();
}
private static DriveService AuthenticateService(string svcEmail, string certPath)
{
string[] scopes = new[] { DriveService.Scope.DriveFile };
X509Certificate2 certificate = new X509Certificate2(certPath, "notasecret", X509KeyStorageFlags.Exportable);
var init = new ServiceAccountCredential.Initializer(svcEmail) { Scopes = scopes };
IConfigurableHttpClientInitializer credential = new ServiceAccountCredential(init.FromCertificate(certificate));
return new DriveService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Document Management Service",
});
}
}
And here's an experimental consumer:
internal class Program
{
private const string svcEmail = "serviceaccountid#developer.gserviceaccount.com";
private const string certPath = #"credentials\projectname-fingerprintprefix.p12";
private readonly static Drive drive = new Drive(svcEmail, certPath);
private static void Main(string[] args)
{
string id = drive.CreateShared("my title").Result;
Console.WriteLine(id);
}
}
This seems to use Google Drive storage in an isolated, application/project-specific data repository. According to other posts, there is no way to get an interactive Drive UI view on that. I don't know if if it uses my personal storage quota, etc. But, this is the best approach I have so-far and I'll answer those questions for myself (not here) next.

How to get Google Drive file ID

I've created a file to Drive root using Google Drive Android API. How can I get this file ID to share it using Google Client Library?
Getting DriveFileResult in ResultCallback<DriveFileResult> callback returns Null:
String fileId = result.getDriveFile().getDriveId().getResourceId();
The callback is to the file being created locally. The DriveId will only have a resourceId when the file is synced to the server. Until then getResourceId will return null.
https://developer.android.com/reference/com/google/android/gms/drive/DriveId.html#getResourceId()
Use CompletionEvents to be notified when syncing with the server has occurred. Then calling getResourceId() should deliver what you are expecting.
this code might help to identify the id for a file present in the google drive.
public static void main(String[] args) throws IOException {
// Build a new authorized API client service.
Drive service = getDriveService();
// Print the names and IDs for up to 10 files.
FileList result = service.files().list()
.setMaxResults(10)
.execute();
List<File> files = result.getItems();
if (files == null || files.size() == 0) {
System.out.println("No files found.");
} else {
System.out.println("Files:");
for (File file : files) {
System.out.printf("%s (%s)\n", file.getTitle(), file.getId());
}
}
}