KoenZomers / OneDriveAPI

API in .NET Framework 4.8.0, .NET Core 3.1 and .NET 6.0 to communicate with OneDrive Personal and OneDrive for Business
Eclipse Public License 1.0
110 stars 34 forks source link

[QUESTION] Is there any way to Upload a file to OneDrive without authentication prompt #34

Closed averied closed 3 years ago

averied commented 3 years ago

Dear @KoenZomers ,

All examples in the Demo Application show Microsoft's Auhentication form when using the "Authorize" button.

I have Office 365 Business Basic, which includes 1TB OneDrive space. So my Office 365 account is like averied@mydomain.com

I generate some PDFs on a daily basis in my PC. I want to create a windows service (.net console app) that takes the PDF files and uploads them to my OneDrive space; obviously, the service cannot fill in a login form. How would I use this API for my use case? Is it possible?. I would like some guidance.

Maybe a long term token or something. The only way I've found is with refresh tokens, but say the users goes on holiday for a month or so. Even the refresh token will expire I guess.

I've found this example that uses GraphAPI in a console APP. Not sure if this type of authentication is implemented in OneDriveAPI:

https://www.c-sharpcorner.com/article/how-to-access-microsoft-graph-api-in-console-application/

Thanks for your help. Best regards.

ne0rrmatrix commented 3 years ago

I have been looking for a way to upload files to a personal OneDrive account and I have a personal office subscription with 1TB of storage. Would be nice if you could add one drive support for personal accounts.

averied commented 3 years ago

Finally, I think I found a way to upload to onedrive (with Graph), withouth user intervention. Basicaly, add permissions in the Azure AD for your app, so you can upload content to any user's drive (Directory.ReadWrite.All, FIle.ReadWrite.All, Group.ReadWrite.All). You can create a ClientSecret that doesn't expire.

This is my test console app:

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Graph;
using Microsoft.Graph.Auth;
using Microsoft.Identity.Client;

namespace ConsoleApp5
{
    class Program
    {
        static void Main(string[] args)
        {

            try
            {
                uploadFileAsync().GetAwaiter().GetResult();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            Console.ReadLine();

        }

        public async static Task uploadFileAsync()
        {
            var clientId = "xxxx";
            var tenantId = "xxxxx";
            var clientSecret = "xxxxxxx";
            IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
                .Create(clientId)
                .WithTenantId(tenantId)
                .WithClientSecret(clientSecret)
                .Build();

            ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
            GraphServiceClient graphClient = new GraphServiceClient(authProvider);

            //To get a user's ID
            //var drives = await graphClient.Users.Request().Filter("mail eq 'myuser@mydomain.com'").GetAsync(); 

            //List contents of root folder
            //var drives = await graphClient.Users["c0561296-9e7a-4c3e-9a15-bf46d7e23b6c"].Drive.Root.Children.Request().GetAsync();

           //CREATE A FOLDER

            var driveItem = new DriveItem
            {
                Name = "032021",
                Folder = new Folder
                {
                },
                AdditionalData = new Dictionary<string, object>()
                {
                    {"@microsoft.graph.conflictBehavior", "rename"}
                }
            };

            await graphClient.Users["c0561296-9e7a-4c3e-9a15-4c2200eefcb6"].Drive.Root.Children
                .Request()
                .AddAsync(driveItem);

            //UPLOAD FILE

            var fileStream = System.IO.File.OpenRead("C:\\DEV\\files\\test.pdf");
            // Use properties to specify the conflict behavior
            // in this case, replace
            var uploadProps = new DriveItemUploadableProperties
            {
                ODataType = null,
                AdditionalData = new Dictionary<string, object>
                {
                    { "@microsoft.graph.conflictBehavior", "replace" }
                }
             };

            // Create the upload session
            // itemPath does not need to be a path to an existing item
            var uploadSession = await graphClient.Users["c0561296-9e7a-4c3e-9a15-bf46d7e23b6c"].Drive.Root
                .ItemWithPath("032021/LargePDF.pdf")
                .CreateUploadSession(uploadProps)
                .Request()
                .PostAsync();

            // Max slice size must be a multiple of 320 KiB
            int maxSliceSize = 320 * 1024;
            var fileUploadTask =
                new LargeFileUploadTask<DriveItem>(uploadSession, fileStream, maxSliceSize);

            // Create a callback that is invoked after each slice is uploaded
            IProgress<long> progress = new Progress<long>(prog => {
                Console.WriteLine($"Uploaded {prog} bytes of {fileStream.Length} bytes");
            });

            try
            {
                // Upload the file
                var uploadResult = await fileUploadTask.UploadAsync(progress);

                if (uploadResult.UploadSucceeded)
                {
                    // The ItemResponse object in the result represents the
                    // created item.
                    Console.WriteLine($"Upload complete, item ID: {uploadResult.ItemResponse.Id}");

                    //CREATE PUBLIC LINK TO SHARE THE FILE

                    var type = "view";

                    var scope = "anonymous";

                    var link = await graphClient.Users["c0561296-9e7a-4c3e-9a15-bf46d7e23b6c"].Drive.Items[uploadResult.ItemResponse.Id]
                        .CreateLink(type, scope, null, null, null)
                        .Request()
                        .PostAsync();

                    Console.WriteLine($"Upload complete, item ID: {link.Link.WebUrl}");
                }
                else
                {
                    Console.WriteLine("Upload failed");
                }
            }
            catch (ServiceException ex)
            {
                Console.WriteLine($"Error uploading: {ex.ToString()}");
            }
        }
    }
}
KoenZomers commented 3 years ago

Hi @averied, I have removed the tenantId, clientId and clientsecret from your response. Never share this publicly. I would strongly urge you to remove the client secret from your application registration as it has been online for 9 days already in this comment. Using it, it gives anyone full control to your entire tenant: your Azure Active Directory, all of your files and all of your Groups. Probably even best to delete the entire application registraton to ensure refresh tokens that have been generated using this pair are also disconnected. Please be careful with sharing these details on public locations.

In your code you're making use of MSAL and the Graph API. Also a perfect combination to gain access to your files, so yes, this can be used as well. My API in this GitHub repo uses the same calls but using its own code calling into the same endpoints.

As for your initial question, indeed, using a refresh token you only authenticate once interactively. After that, the refresh token will give you access without needing user intervention. Refresh tokens by default are valid for 90 days counting from the moment they have been created. If you at least use it once during those 90 days to generate a new refresh token, that new refresh token is valid for 90 days again from the moment that was generated. So you can use it indefinitely. More information on these token lifetimes can be found here: https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-configurable-token-lifetimes

KoenZomers commented 3 years ago

Closing as no further comments or feedback has been received.