Karthik Ramesh
Karthik Ramesh

Reputation: 17

Unable to use ApplyAuthenticationToRequest call with latest GDrive code

I am working on a GDrive project which was written over 10 months back. The previous code to get the Drive service object was

private OAuth2Authenticator<AssertionFlowClient> authenticator;
private DriveService GetService(String userEmail)
    {
        X509Certificate2 certificate = new X509Certificate2(SERVICE_ACCOUNT_PKCS12_FILE_PATH, "notasecret", X509KeyStorageFlags.Exportable);

        var provider = new AssertionFlowClient(GoogleAuthenticationServer.Description, certificate)
        {
            ServiceAccountId = SERVICE_ACCOUNT_EMAIL,
            Scope = DriveService.Scopes.Drive.GetStringValue(),
            ServiceAccountUser = userEmail,
        };

        authenticator = new OAuth2Authenticator<AssertionFlowClient>(provider, AssertionFlowClient.GetState);

        return new DriveService(new BaseClientService.Initializer() { Authenticator = authenticator, ApplicationName = "Random" });
    }

With the latest google drive api changes i changed the code to get the drive service object to

private DriveService GetService(String userEmail)
    {
        X509Certificate2 certificate = new X509Certificate2(SERVICE_ACCOUNT_PKCS12_FILE_PATH, "notasecret", X509KeyStorageFlags.Exportable);

        ServiceAccountCredential credential = new ServiceAccountCredential(
       new ServiceAccountCredential.Initializer(SERVICE_ACCOUNT_EMAIL)
       {
           Scopes = new[] { DriveService.Scope.Drive },
           User = userEmail,
       }.FromCertificate(certificate));

        // Create the service.
        return new DriveService(new BaseClientService.Initializer()
        {
            HttpClientInitializer = credential,
            ApplicationName = "Random",
        });

    }

I also have the following code to Download a file

 public void DownloadFile(string url, string fileName)
    {
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);

        //authenticator.ApplyAuthenticationToRequest(request);

        HttpWebResponse response = (HttpWebResponse)request.GetResponse();
        System.IO.Stream received = response.GetResponseStream();

        using (System.IO.FileStream file = new System.IO.FileStream(fileName, System.IO.FileMode.Create, System.IO.FileAccess.Write))
        {
            Util.CopyStream(received, file);
        }
    }

The problem here is that with the latest change to get the drive service object i am not able to call the "ApplyAuthenticationToRequest" call since OAuth2Authenticator is deprecated. Without calling that function i get an exception that "System.Net.WebException: The remote server returned an error: (401) Unauthorized". With the latest changes how can i call the "ApplyAuthenticationToRequest" function so that the call could succeed. This function is also used in 2 other places in the project.Can someone please help?

Upvotes: 0

Views: 524

Answers (2)

sp00k
sp00k

Reputation: 80

apperently it's also possible with the service

        var file = service.Files.Get(fileID).Execute();
        var bytes = service.HttpClient.GetByteArrayAsync(file.DownloadUrl).Result;

Upvotes: 4

sp00k
sp00k

Reputation: 80

you need to request a token from google.. here's the class to get a token from google

public class GoogleJsonWebToken
{
    public const string SCOPE_DRIVE_READONLY = "https://www.googleapis.com/auth/drive.readonly";
    public const string SCOPE_DRIVE = "https://www.googleapis.com/auth/drive";

    public static dynamic GetAccessToken(string clientIdEMail, string keyFilePath, string scope)
    {
        // certificate
        var certificate = new X509Certificate2(keyFilePath, "notasecret");

        // header
        var header = new { typ = "JWT", alg = "RS256" };

        // claimset
        var times = GetExpiryAndIssueDate();
        var claimset = new
        {
            iss = clientIdEMail,
            scope = scope,
            aud = "https://accounts.google.com/o/oauth2/token",
            iat = times[0],
            exp = times[1],
        };

        JavaScriptSerializer ser = new JavaScriptSerializer();

        // encoded header
        var headerSerialized = ser.Serialize(header);
        var headerBytes = Encoding.UTF8.GetBytes(headerSerialized);
        var headerEncoded = Convert.ToBase64String(headerBytes);

        // encoded claimset
        var claimsetSerialized = ser.Serialize(claimset);
        var claimsetBytes = Encoding.UTF8.GetBytes(claimsetSerialized);
        var claimsetEncoded = Convert.ToBase64String(claimsetBytes);

        // input
        var input = headerEncoded + "." + claimsetEncoded;
        var inputBytes = Encoding.UTF8.GetBytes(input);

        // signiture
        var rsa = certificate.PrivateKey as RSACryptoServiceProvider;
        var cspParam = new CspParameters
        {
            KeyContainerName = rsa.CspKeyContainerInfo.KeyContainerName,
            KeyNumber = rsa.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2
        };
        var aescsp = new RSACryptoServiceProvider(cspParam) { PersistKeyInCsp = false };
        var signatureBytes = aescsp.SignData(inputBytes, "SHA256");
        var signatureEncoded = Convert.ToBase64String(signatureBytes);

        // jwt
        var jwt = headerEncoded + "." + claimsetEncoded + "." + signatureEncoded;

        var client = new WebClient();
        client.Encoding = Encoding.UTF8;
        var uri = "https://accounts.google.com/o/oauth2/token";
        var content = new NameValueCollection();

        content["assertion"] = jwt;
        content["grant_type"] = "urn:ietf:params:oauth:grant-type:jwt-bearer";

        string response = Encoding.UTF8.GetString(client.UploadValues(uri, "POST", content));

        var result = ser.Deserialize<dynamic>(response);

        return result;
    }

    private static int[] GetExpiryAndIssueDate()
    {
        var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
        var issueTime = DateTime.UtcNow;

        var iat = (int)issueTime.Subtract(utc0).TotalSeconds;
        var exp = (int)issueTime.AddMinutes(55).Subtract(utc0).TotalSeconds;

        return new[] { iat, exp };
    }

}

and for the download itself i'm using this

            public static byte[] GetFile(string fileID, out string contenttype, out string filename)
    {
        DALParameter dalp = new DALParameter();
        var gdEmail = dalp.GetParameter(INMDTO.Parameters.GoogleDriveLogin, INMDTO.ParametersLevel.General, 0);
        var credential = GetCertificate();
        var service = new DriveService(new BaseClientService.Initializer()
        {
            HttpClientInitializer = credential,
            ApplicationName = "tttt",
        });
        string path = AppDomain.CurrentDomain.BaseDirectory + "App_Data\\";
        string certFile = "gd.p12";
        string fullcertpath = Path.Combine(path, certFile);
        var email = gdEmail;
        var auth = GoogleJsonWebToken.GetAccessToken(email, fullcertpath, GoogleJsonWebToken.SCOPE_DRIVE_READONLY);

        var f = service.Files.Get(fileID).Execute();
        contenttype = f.MimeType;
        filename = f.OriginalFilename;
        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(new Uri(f.DownloadUrl));
        req.Headers.Add("Authorization", auth["token_type"] + " " + auth["access_token"]);
        HttpWebResponse response = (HttpWebResponse)req.GetResponse();
        if (response.StatusCode == HttpStatusCode.OK)
        {
            Stream sourceStream = response.GetResponseStream(); // the ConnectStream
            byte[] array;
            using (var ms = new MemoryStream())
            {
                sourceStream.CopyTo(ms);
                array = ms.ToArray();
            }
            return array;
        }
        return null;
    }

it might not be the best solution but it works and i didn't really find another way to do it. hope this helps you a little.

Upvotes: 0

Related Questions