GoogleWebAuthorizationBroker.AuthorizeAsync Hangs

2019-01-06 20:12发布

问题:

Our website needs to upload videos to youtube from the code behind (asp.net mvc application). I'm trying to get the google credentials, but when i call the AuthorizeAsync, the application just hangs. I've looked all over for a solution and none seem to help out. I've already searched for the obvious on google and stack overflow. most of what i found mentioned that the application might not have access the the appdata folder, so i tried changing the folder to be in the c drive, d drive and in the actual inetpub location. i tested and found i was able to have the application write to those locations.

to be more specific, the user is our admin, customers upload videos to us, and the admin approves them. when the admin approves them, it is posted on our youtube account. the admin should not have to do anything but click the approve button.

To make this an actual question, what can i do to get past the AuthorizeAsync? Let me know if you need more info

        UserCredential credential;
        GoogleWebAuthorizationBroker.Folder = "YouTube.Auth.Store";
        using (var stream = new FileStream(CredentialsPath, FileMode.Open,
                             FileAccess.Read))
        {
            credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
                GoogleClientSecrets.Load(stream).Secrets,
                // This OAuth 2.0 access scope allows an application to upload files to the
                // authenticated user's YouTube channel, but doesn't allow other types of access.
                new[] { YouTubeService.Scope.YoutubeUpload },
                "user",
                CancellationToken.None,
                new FileDataStore("YouTube.Auth.Store")
            ).Result;
        }

回答1:

Found a way to get passed this.

I used GoogleAuthorizationCodeFlow instead. this is what it turned out to look like:

        ClientSecrets secrets = new ClientSecrets()
        {
            ClientId = CLIENT_ID,
            ClientSecret = CLIENT_SECRET
        };

        var token = new TokenResponse { RefreshToken = REFRESH_TOKEN }; 
        var credentials = new UserCredential(new GoogleAuthorizationCodeFlow(
            new GoogleAuthorizationCodeFlow.Initializer 
            {
                ClientSecrets = secrets
            }), 
            "user", 
            token);

        var service = new YouTubeService(new BaseClientService.Initializer()
        {
            HttpClientInitializer = credentials,
            ApplicationName = "TestProject"
        });


回答2:

It is possible to break out if this, for example using a timeout, because the AuthorizeAsync method has the implementation of a CancellationToken.

  • Create a TokenSource to obtain the Token itself.
  • Set the Cancellation of the TokenSource to i.e. 20 Seconds
  • Extract the token to pass to the AuthorizeAsync method.

public async void RunAsync()
{
    UserCredential credential;
    var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read);

    CancellationTokenSource cts = new CancellationTokenSource();
    cts.CancelAfter(TimeSpan.FromSeconds(20));
    CancellationToken ct = cts.Token;

    credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
    GoogleClientSecrets.Load(stream).Secrets,
    new[] { YouTubeService.Scope.YoutubeUpload },
    "user",
    ct
    );

    if (ct.IsCancellationRequested) return;

    // do more stuff when came back authorized.
}

Now, after 20 seconds, when the AuthorizeAsync was not completed, the caller of this method RunAsync will receive an Exception.

try
{
    await RunAsync();
}
catch(Exception)
{
     // "Timeout" of the RunAsync(); 
}


回答3:

GoogleWebAuthorizationBroker.Folder = "YouTube.Auth.Store";

This line will generate a access token in YouTube.Auth.Store/xxx, and you need to right click this file -> properties -> Copy to Output Directory -> Copy always in visual studio

it works for me.



回答4:

The following code lets you use your google drive api credentials to request login information from the user, and download the file to your server to perform some request:

Option Strict On
Option Explicit On
Imports System.Net
Imports System.IO
Imports System.Linq
Imports System.Threading
Imports System.Web
Imports System.Web.UI.WebControls
Imports Google
Imports Google.Apis.Auth.OAuth2
Imports Google.Apis.Auth.OAuth2.Flows
Imports Google.Apis.Auth.OAuth2.Web
Imports Google.Apis.Services

'for drive
Imports Google.Apis.Drive.v2
Imports Google.Apis.Drive.v2.Data
Imports Google.Apis.Util.Store
'for AuthorizationCodeRequestUrl
Imports Google.Apis.Auth.OAuth2.Requests
Imports System.Drawing

Public Class _Default
    Inherits Page

    Private redirect_uri As String = ""
    Private client_id As String = ""
    Private client_secret As String = ""
    Private service As DriveService

    'Application logic should manage users authentication. 
    'This sample works with only one user. You can change it by retrieving
    'data from the session.
    'You use the word user because you are authorized by the user to access
    'their drive contents.
Private UserId As String = "user" 

Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load

    Dim SCOPES As String() = New [String]() {"https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile", "https://www.googleapis.com/auth/drive.install"}
    Dim flow As GoogleAuthorizationCodeFlow
    'use extended class to create google authorization code flow
    'use custom own datastore
    flow = New ForceOfflineGoogleAuthorizationCodeFlow(New GoogleAuthorizationCodeFlow.Initializer() With {
    .DataStore = New FileDataStore("D:\inetpub\wwwroot\go\filedatastore", True),
    .ClientSecrets = New ClientSecrets() With {
        .ClientId = client_id,
        .ClientSecret = client_secret
    },
    .Scopes = SCOPES
})

    Dim uri = Request.Url.ToString()
    Dim redirecturi As String = redirect_uri
    Dim code = Request("code")
    If code IsNot Nothing Then
        output.Text &= "<br />Code: " & code & "<br />"
        output.Text &= "<br />UserID: " & UserId & "<br />"
        output.Text &= "<br />URI: " & uri & "<br />"
        output.Text &= "<br />RedirectURI: " & redirecturi & "<br />"
        output.Text &= "<br />uri-substring-thing: " & uri.Substring(0, uri.IndexOf("?")) & "<br />"
        Dim token = flow.ExchangeCodeForTokenAsync(UserId, code, redirecturi, CancellationToken.None).Result

        ' Extract the right state.
        Dim oauthState = AuthWebUtility.ExtracRedirectFromState(flow.DataStore, UserId, Request("state")).Result
        Response.Redirect(oauthState)
    Else

        Dim result = New AuthorizationCodeWebApp(flow, redirecturi, uri).AuthorizeAsync(UserId, CancellationToken.None).Result

        If result.RedirectUri IsNot Nothing Then
            ' Redirect the user to the authorization server.
            Response.Redirect(result.RedirectUri)
        Else
            ' The data store contains the user credential, so the user has been already authenticated.


            service = New DriveService(New BaseClientService.Initializer() With {
            .HttpClientInitializer = result.Credential,
            .ApplicationName = "Drive API Sample"
        })
        End If
    End If

End Sub

''' <summary>
''' Download a file
''' Documentation: https://developers.google.com/drive/v2/reference/files/get
''' </summary>
''' <param name="_service">a Valid authenticated DriveService</param>
''' <param name="_fileResource">File resource of the file to download</param>
''' <param name="_saveTo">location of where to save the file including the file name to save it as.</param>
''' <returns></returns>
Public Shared Function downloadFile(_service As DriveService, _fileResource As Data.File, _saveTo As String) As [Boolean]

    If Not [String].IsNullOrEmpty(_fileResource.DownloadUrl) Then
        Try
            Dim x = _service.HttpClient.GetByteArrayAsync(_fileResource.DownloadUrl)
            Dim arrBytes As Byte() = x.Result
            System.IO.File.WriteAllBytes(_saveTo, arrBytes)
            Return True
        Catch e As Exception
            Console.WriteLine("An error occurred: " + e.Message)
            Return False
        End Try
    Else
        ' The file doesn't have any content stored on Drive.
        Return False
    End If
End Function

Public Async Function GetFile(IncomingFileID As String) As Tasks.Task
    Try
        Dim request As FilesResource.GetRequest = service.Files.Get(IncomingFileID)
        Dim response As Data.File = Await request.ExecuteAsync(CancellationToken.None)
        downloadFile(service, response, "D:/inetpub/wwwroot/go/" & Guid.NewGuid.ToString)

    Catch ex As Exception
        Dim str As String = "<br />" & ex.ToString()
        str = str.Replace(Environment.NewLine, Environment.NewLine + "<br/>")
        str = str.Replace("  ", " &nbsp;")
        output.Text &= String.Format("<font color=""red"">{0}</font>", str)
    End Try
End Function


'''<summary>Gets the File Lists of the user.</summary>
Public Async Function FetchFilelists() As System.Threading.Tasks.Task
    Try
        ' Get all files of the user asynchronously.
        Dim request As FilesResource.ListRequest = service.Files.List()

        'get only my files
        request.Q = "mimeType contains 'image/'"

        Dim response As FileList = Await request.ExecuteAsync()

        ShowFilelists(response)
    Catch ex As Exception
        Dim str = ex.ToString()
        str = str.Replace(Environment.NewLine, Environment.NewLine + "<br/>")
        str = str.Replace("  ", " &nbsp;")
        output.Text = String.Format("<font color=""red"">{0}</font>", str)
    End Try
End Function

Private Sub ShowFilelists(response As FileList)
    If response.Items Is Nothing Then
        output.Text &= "You have no files!<br/>"
        Return
    End If

    output.Text &= "Showing files...<br/>"
    Dim ctr As Integer = 0
    For Each file As Google.Apis.Drive.v2.Data.File In response.Items
        ctr += 1
        Dim listPanel As New Panel() With {
            .BorderWidth = Unit.Pixel(1),
            .BorderColor = Color.Black
        }
        listPanel.Controls.Add(New Label() With {
            .Text = file.Title + "--" + file.MimeType
                })
        listPanel.Controls.Add(New Label() With {
            .Text = "<hr/>"
            })


        If file.MimeType.ToString().Contains("folder") = False Then
            listPanel.Controls.Add(New HyperLink() With {
            .Text = "Download",
            .NavigateUrl = file.WebContentLink
        })

            listPanel.Controls.Add(New Button() With {
                                   .Text = "Use This Image", .OnClientClick = "document.getElementById('currentImageId').value = '" & file.Id & "';return false;"})

        End If
        lists.Controls.Add(listPanel)
    Next

    output.Text &= "Total Files: " & ctr.ToString
End Sub



Protected Async Sub listButton_Click(sender As Object, e As EventArgs) Handles listbutton.Click
    Await FetchFilelists()
End Sub

Protected Async Sub DownloadImageButton_Click(sender As Object, e As EventArgs) Handles DownloadImageButton.Click
    Dim idToUse As String = Page.Request.Form(currentImageId.UniqueID)

    output.Text &= "<br />Attempting to download id: " & idToUse & "<br />"
    Await GetFile(idToUse)
    output.Text &= "<br />Download Completed<br />"
End Sub

'overwrite. force offline.
Friend Class ForceOfflineGoogleAuthorizationCodeFlow
    Inherits GoogleAuthorizationCodeFlow
    'source: //gotoanswer.stanford.edu/google_analytics_oauth_with_accesstype__offline_in_c-4478263/

    Public Sub New(initializer As GoogleAuthorizationCodeFlow.Initializer)
        MyBase.New(initializer)
    End Sub

    Public Overrides Function CreateAuthorizationCodeRequest(redirectUri As String) As AuthorizationCodeRequestUrl
        Dim ss = New Google.Apis.Auth.OAuth2.Requests.GoogleAuthorizationCodeRequestUrl(New Uri(AuthorizationServerUrl))
        ss.AccessType = "offline"
        ss.ApprovalPrompt = "force"
        ss.ClientId = ClientSecrets.ClientId
        ss.Scope = String.Join(" ", Scopes)
        ss.RedirectUri = redirectUri
        Return ss
    End Function
End Class


End Class

Here's the WebForm .aspx page:

<%@ Page Title="Home Page" Language="VB" MasterPageFile="~/Site.Master"
AutoEventWireup="true" CodeBehind="Default.aspx.vb"     Inherits="PictureMyCard._Default" Async="True" %>

<asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" 
runat="server">


<div>
    <asp:Button ID="listbutton" runat="server" ClientIDMode="Static" Text="list" />
<asp:Label id="output" runat="server" ClientIDMode="Static" Text="..." ></asp:Label>
</div>
    <asp:Label ID="lists" ClientIDMode="Static" Text="..." runat="server"></asp:Label>
<hr />

<asp:HiddenField ClientIDMode="Static" ID="currentImageId" runat="server" />

<asp:Button runat="server" ID="DownloadImageButton" ClientIDMode="Static"  Text="Download Image to server" />
</asp:Content>