• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Jericho / ZoomNet / 834

15 Nov 2024 03:18PM UTC coverage: 20.194% (-0.2%) from 20.435%
834

push

appveyor

Jericho
Merge branch 'release/0.84.0'

645 of 3194 relevant lines covered (20.19%)

11.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.71
/Source/ZoomNet/Utilities/OAuthTokenHandler.cs
1
using Pathoschild.Http.Client;
2
using Pathoschild.Http.Client.Extensibility;
3
using System;
4
using System.Collections.Generic;
5
using System.Collections.ObjectModel;
6
using System.Linq;
7
using System.Net.Http;
8
using System.Net.Http.Headers;
9
using System.Text;
10
using System.Text.Json;
11
using System.Threading;
12
using ZoomNet.Models;
13

14
namespace ZoomNet.Utilities
15
{
16
        /// <summary>
17
        /// Handler to ensure requests to the Zoom API include a valid OAuth token.
18
        /// </summary>
19
        /// <seealso cref="IHttpFilter" />
20
        internal class OAuthTokenHandler : IHttpFilter, ITokenHandler
21
        {
22
                public string Token
23
                {
24
                        get
25
                        {
26
                                RefreshTokenIfNecessary(false);
×
27
                                return _connectionInfo.AccessToken;
×
28
                        }
29
                }
30

31
                public IConnectionInfo ConnectionInfo
32
                {
33
                        get => _connectionInfo;
×
34
                }
35

36
                private static readonly ReaderWriterLockSlim _lock = new();
1✔
37

38
                private readonly OAuthConnectionInfo _connectionInfo;
39
                private readonly HttpClient _httpClient;
40
                private readonly TimeSpan _clockSkew;
41

42
                public OAuthTokenHandler(OAuthConnectionInfo connectionInfo, HttpClient httpClient, TimeSpan? clockSkew = null)
43
                {
44
                        if (connectionInfo == null) throw new ArgumentNullException(nameof(connectionInfo));
3✔
45
                        if (string.IsNullOrEmpty(connectionInfo.ClientId)) throw new ArgumentNullException("connectionInfo.ClientId");
3✔
46
                        if (string.IsNullOrEmpty(connectionInfo.ClientSecret)) throw new ArgumentNullException("connectionInfo.ClientSecret");
3✔
47
                        if (httpClient == null) throw new ArgumentNullException(nameof(httpClient));
3✔
48

49
                        _connectionInfo = connectionInfo;
3✔
50
                        _httpClient = httpClient;
3✔
51
                        _clockSkew = clockSkew.GetValueOrDefault(TimeSpan.FromMinutes(5));
3✔
52
                }
3✔
53

54
                /// <summary>Method invoked just before the HTTP request is submitted. This method can modify the outgoing HTTP request.</summary>
55
                /// <param name="request">The HTTP request.</param>
56
                public void OnRequest(IRequest request)
57
                {
58
                        // Do not overwrite the Authorization header if it is already set.
59
                        // One example where it's important to preserve the Authorization
60
                        // header is CloudRecordings.DownloadFileAsync where developers can
61
                        // specify a custom bearer token which must take precedence.
62
                        if (request.Message.Headers.Authorization == null) request.WithBearerAuthentication(Token);
1✔
63
                }
1✔
64

65
                /// <summary>Method invoked just after the HTTP response is received. This method can modify the incoming HTTP response.</summary>
66
                /// <param name="response">The HTTP response.</param>
67
                /// <param name="httpErrorAsException">Whether HTTP error responses should be raised as exceptions.</param>
68
                public void OnResponse(IResponse response, bool httpErrorAsException) { }
1✔
69

70
                public string RefreshTokenIfNecessary(bool forceRefresh)
71
                {
72
                        try
73
                        {
74
                                _lock.EnterUpgradeableReadLock();
3✔
75

76
                                if (forceRefresh || TokenIsExpired())
3✔
77
                                {
78
                                        try
79
                                        {
80
                                                _lock.EnterWriteLock();
3✔
81

82
                                                if (forceRefresh || TokenIsExpired())
3✔
83
                                                {
84
                                                        var contentValues = new Dictionary<string, string>()
3✔
85
                                                        {
3✔
86
                                                                { "grant_type", _connectionInfo.GrantType.ToEnumString() },
3✔
87
                                                        };
3✔
88

89
                                                        switch (_connectionInfo.GrantType)
3✔
90
                                                        {
91
                                                                case OAuthGrantType.AccountCredentials:
92
                                                                        contentValues.Add("account_id", _connectionInfo.AccountId);
1✔
93
                                                                        break;
1✔
94
                                                                case OAuthGrantType.AuthorizationCode:
95
                                                                        contentValues.Add("code", _connectionInfo.AuthorizationCode);
2✔
96
                                                                        if (!string.IsNullOrEmpty(_connectionInfo.RedirectUri)) contentValues.Add("redirect_uri", _connectionInfo.RedirectUri);
2✔
97
                                                                        if (!string.IsNullOrEmpty(_connectionInfo.CodeVerifier)) contentValues.Add("code_verifier", _connectionInfo.CodeVerifier);
2✔
98
                                                                        break;
×
99
                                                                case OAuthGrantType.RefreshToken:
100
                                                                        contentValues.Add("refresh_token", _connectionInfo.RefreshToken);
×
101
                                                                        break;
102
                                                        }
103

104
                                                        var requestTime = DateTime.UtcNow;
3✔
105
                                                        var request = new HttpRequestMessage(HttpMethod.Post, "https://api.zoom.us/oauth/token");
3✔
106
                                                        request.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{_connectionInfo.ClientId}:{_connectionInfo.ClientSecret}")));
3✔
107
                                                        request.Content = new FormUrlEncodedContent(contentValues);
3✔
108
                                                        var response = _httpClient.SendAsync(request).ConfigureAwait(false).GetAwaiter().GetResult();
3✔
109
                                                        var responseContent = response.Content.ReadAsStringAsync(null).ConfigureAwait(false).GetAwaiter().GetResult();
3✔
110

111
                                                        if (string.IsNullOrEmpty(responseContent)) throw new Exception(response.ReasonPhrase);
3✔
112

113
                                                        var jsonResponse = JsonDocument.Parse(responseContent).RootElement;
3✔
114

115
                                                        if (!response.IsSuccessStatusCode)
3✔
116
                                                        {
117
                                                                var reason = jsonResponse.GetPropertyValue("reason", "The Zoom API did not provide a reason");
2✔
118
                                                                throw new ZoomException(reason, response, "No diagnostic available", null);
2✔
119
                                                        }
120

121
                                                        _connectionInfo.RefreshToken = jsonResponse.GetPropertyValue("refresh_token", string.Empty);
1✔
122
                                                        _connectionInfo.AccessToken = jsonResponse.GetPropertyValue("access_token", string.Empty);
1✔
123
                                                        _connectionInfo.TokenExpiration = requestTime.AddSeconds(jsonResponse.GetPropertyValue("expires_in", 60 * 60));
1✔
124
                                                        _connectionInfo.Scopes = new ReadOnlyCollection<string>(jsonResponse.GetPropertyValue("scope", string.Empty).Split(' ').OrderBy(x => x).ToList());
1✔
125

126
                                                        // Please note that Server-to-Server OAuth does not use the refresh token.
127
                                                        // Therefore change the grant type to 'RefreshToken' only when the response includes a refresh token.
128
                                                        if (!string.IsNullOrEmpty(_connectionInfo.RefreshToken)) _connectionInfo.GrantType = OAuthGrantType.RefreshToken;
2✔
129

130
                                                        _connectionInfo.OnTokenRefreshed?.Invoke(_connectionInfo.RefreshToken, _connectionInfo.AccessToken);
1✔
131
                                                }
132
                                        }
×
133
                                        finally
134
                                        {
135
                                                if (_lock.IsWriteLockHeld) _lock.ExitWriteLock();
6✔
136
                                        }
3✔
137
                                }
138
                        }
×
139
                        finally
140
                        {
141
                                if (_lock.IsUpgradeableReadLockHeld) _lock.ExitUpgradeableReadLock();
6✔
142
                        }
3✔
143

144
                        return _connectionInfo.AccessToken;
1✔
145
                }
146

147
                private bool TokenIsExpired()
148
                {
149
                        return _connectionInfo.TokenExpiration <= DateTime.UtcNow.Add(_clockSkew);
×
150
                }
151
        }
152
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc