Jericho / ZoomNet

.NET client library for the Zoom.us REST API v2
MIT License
69 stars 45 forks source link

Unauthorized status code on CloudRecordings.DownloadFileAsync invocation. #348

Open pvgritsenko-ansible opened 5 days ago

pvgritsenko-ansible commented 5 days ago

Sometimes when we try to download Zoom recording file we encounter following Pathoschild.Http.Client.ApiException with status code 'Unauthorized'. The only idea is access token expired and for some reasone it was not successfuly updated by OAuthTokenHandler.

And may it be possible that the source of issue is ZoomRetryCoordinator.ExecuteAsync method where we check the responseContent.message contains "access token is expired" string. I've simulated the exception and noted that actually message is empty. Can it be empty just because we cannot read stream content?

Full stack-trace:

Exception: Pathoschild.Http.Client.ApiException: The API query failed with status code Unauthorized: Unauthorized
   at Pathoschild.Http.Client.Extensibility.DefaultErrorFilter.OnResponse(IResponse response, Boolean httpErrorAsException)
   at Pathoschild.Http.Client.Internal.Request.Execute()
   at Pathoschild.Http.Client.Internal.Request.AsStream()
   at ZoomNet.Resources.CloudRecordings.DownloadFileAsync(String downloadUrl, CancellationToken cancellationToken)
Jericho commented 4 days ago

Are you able to capture the response from Zoom (using a tool such as Fiddler for example)? This would allow us to validate your theory rather than just guessing.

Jericho commented 4 days ago

I wrote a unit test to simulate the scenario in your theory and I am not able to reproduce the problem, the unit test completes successfully. This seems to indicate that the problem you are experiencing is not related to an expired token (or that my unit test does not reflect your scenario accurately).

/// <summary>
/// This unit test simulates a scenario where we attempt to download a file but our oAuth token has expired.
/// In this situation, we expect the token to be refreshed and the download request to be reissued.
/// </summary>
/// <returns></returns>
[Fact]
public async Task DownloadFileAsync_with_expired_token()
{
    // Arrange
    var downloadUrl = "http://dummywebsite.com/dummyfile.txt";

    var mockTokenHttp = new MockHttpMessageHandler();
    mockTokenHttp // Issue a new token
        .When(HttpMethod.Post, "https://api.zoom.us/oauth/token")
        .Respond(HttpStatusCode.OK, "application/json", "{\"refresh_token\":\"new refresh token\",\"access_token\":\"new access token\"}");

    var mockHttp = new MockHttpMessageHandler();
    mockHttp // This first time the file is requested, we get a 401 Unauthorized. This simulates an expired token.
        .Expect(HttpMethod.Get, downloadUrl)
        .Respond(HttpStatusCode.Unauthorized, new StringContent("{\"message\":\"access token is expired\"}"));
    mockHttp // This second time the file is requested, we get a 200 OK with the file content.
        .Expect(HttpMethod.Get, downloadUrl)
        .Respond(HttpStatusCode.OK, new StringContent("This is the content of the file"));

    var client = Utils.GetFluentClient(mockHttp, mockTokenHttp);
    var recordings = new CloudRecordings(client);

    // Act
    var result = await recordings.DownloadFileAsync(downloadUrl, CancellationToken.None).ConfigureAwait(true);

    // Assert
    mockHttp.VerifyNoOutstandingExpectation();
    mockHttp.VerifyNoOutstandingRequest();
    result.ShouldNotBeNull();
}

Is it possible that the Unauthorized response is legitimate and you are simply not authorized to download this file? I know that Zoom documentation mentions something about an access token specific to downloading file and different than your own access token:

If a user has authorized and installed your OAuth app that contains recording scopes, use the download_access_token or the the user's OAuth access token to download the file, and set the access_token as a Bearer token in the Authorization header.

ZoomNet currently does not support this alternate token and always uses the token that was obtained when initiating your OAuth session. Maybe this is the scenario you are facing? Maybe your access token is not sufficient to download this file? I'm just speculating.