Added support for proxy configs in auth flows, closes #360

This commit is contained in:
Jonas Dellinger 2019-07-17 17:39:51 +02:00
parent 0a9499c7d1
commit 4933deaf26
5 changed files with 63 additions and 45 deletions

View File

@ -16,6 +16,8 @@ namespace SpotifyAPI.Web.Auth
{ {
public string SecretId { get; set; } public string SecretId { get; set; }
public ProxyConfig ProxyConfig { get; set; }
public AuthorizationCodeAuth(string redirectUri, string serverUri, Scope scope = Scope.None, string state = "") public AuthorizationCodeAuth(string redirectUri, string serverUri, Scope scope = Scope.None, string state = "")
: base("code", "AuthorizationCodeAuth", redirectUri, serverUri, scope, state) : base("code", "AuthorizationCodeAuth", redirectUri, serverUri, scope, state)
{ {
@ -53,7 +55,8 @@ namespace SpotifyAPI.Web.Auth
new KeyValuePair<string, string>("refresh_token", refreshToken) new KeyValuePair<string, string>("refresh_token", refreshToken)
}; };
HttpClient client = new HttpClient(); HttpClientHandler handler = ProxyConfig.CreateClientHandler(ProxyConfig);
HttpClient client = new HttpClient(handler);
client.DefaultRequestHeaders.Add("Authorization", GetAuthHeader()); client.DefaultRequestHeaders.Add("Authorization", GetAuthHeader());
HttpContent content = new FormUrlEncodedContent(args); HttpContent content = new FormUrlEncodedContent(args);

View File

@ -14,6 +14,8 @@ namespace SpotifyAPI.Web.Auth
public string ClientId { get; set; } public string ClientId { get; set; }
public ProxyConfig ProxyConfig { get; set; }
public CredentialsAuth(string clientId, string clientSecret) public CredentialsAuth(string clientId, string clientSecret)
{ {
ClientId = clientId; ClientId = clientId;
@ -29,7 +31,8 @@ namespace SpotifyAPI.Web.Auth
new KeyValuePair<string, string>("grant_type", "client_credentials") new KeyValuePair<string, string>("grant_type", "client_credentials")
}; };
HttpClient client = new HttpClient(); HttpClientHandler handler = ProxyConfig.CreateClientHandler(ProxyConfig);
HttpClient client = new HttpClient(handler);
client.DefaultRequestHeaders.Add("Authorization", $"Basic {auth}"); client.DefaultRequestHeaders.Add("Authorization", $"Basic {auth}");
HttpContent content = new FormUrlEncodedContent(args); HttpContent content = new FormUrlEncodedContent(args);

View File

@ -22,30 +22,34 @@ namespace SpotifyAPI.Web.Auth
/// </summary> /// </summary>
public class TokenSwapAuth : SpotifyAuthServer<AuthorizationCode> public class TokenSwapAuth : SpotifyAuthServer<AuthorizationCode>
{ {
string exchangeServerUri; readonly string _exchangeServerUri;
/// <summary> /// <summary>
/// The HTML to respond with when the callback server (serverUri) is reached. The default value will close the window on arrival. /// The HTML to respond with when the callback server (serverUri) is reached. The default value will close the window on arrival.
/// </summary> /// </summary>
public string HtmlResponse { get; set; } = "<script>window.close();</script>"; public string HtmlResponse { get; set; } = "<script>window.close();</script>";
/// <summary> /// <summary>
/// If true, will time how long it takes for access to expire. On expiry, the <see cref="OnAccessTokenExpired"/> event fires. /// If true, will time how long it takes for access to expire. On expiry, the <see cref="OnAccessTokenExpired"/> event fires.
/// </summary> /// </summary>
public bool TimeAccessExpiry { get; set; } public bool TimeAccessExpiry { get; set; }
public ProxyConfig ProxyConfig { get; set; }
/// <param name="exchangeServerUri">The URI to an exchange server that will perform the key exchange.</param> /// <param name="exchangeServerUri">The URI to an exchange server that will perform the key exchange.</param>
/// <param name="serverUri">The URI to host the server at that your exchange server should return the authorization code to by GET request. (e.g. http://localhost:4002)</param> /// <param name="serverUri">The URI to host the server at that your exchange server should return the authorization code to by GET request. (e.g. http://localhost:4002)</param>
/// <param name="scope"></param> /// <param name="scope"></param>
/// <param name="state">Stating none will randomly generate a state parameter.</param> /// <param name="state">Stating none will randomly generate a state parameter.</param>
/// <param name="htmlResponse">The HTML to respond with when the callback server (serverUri) is reached. The default value will close the window on arrival.</param> /// <param name="htmlResponse">The HTML to respond with when the callback server (serverUri) is reached. The default value will close the window on arrival.</param>
public TokenSwapAuth(string exchangeServerUri, string serverUri, Scope scope = Scope.None, string state = "", string htmlResponse = "") : base("code", "", "", serverUri, scope, state) public TokenSwapAuth(string exchangeServerUri, string serverUri, Scope scope = Scope.None, string state = "",
string htmlResponse = "") : base("code", "", "", serverUri, scope, state)
{ {
if (!string.IsNullOrEmpty(htmlResponse)) if (!string.IsNullOrEmpty(htmlResponse))
{ {
HtmlResponse = htmlResponse; HtmlResponse = htmlResponse;
} }
this.exchangeServerUri = exchangeServerUri; _exchangeServerUri = exchangeServerUri;
} }
protected override void AdaptWebServer(WebServer webServer) protected override void AdaptWebServer(WebServer webServer)
@ -55,7 +59,7 @@ namespace SpotifyAPI.Web.Auth
public override string GetUri() public override string GetUri()
{ {
StringBuilder builder = new StringBuilder(exchangeServerUri); StringBuilder builder = new StringBuilder(_exchangeServerUri);
builder.Append("?"); builder.Append("?");
builder.Append("response_type=code"); builder.Append("response_type=code");
builder.Append("&state=" + State); builder.Append("&state=" + State);
@ -64,8 +68,6 @@ namespace SpotifyAPI.Web.Auth
return Uri.EscapeUriString(builder.ToString()); return Uri.EscapeUriString(builder.ToString());
} }
static readonly HttpClient httpClient = new HttpClient();
/// <summary> /// <summary>
/// The maximum amount of times to retry getting a token. /// The maximum amount of times to retry getting a token.
/// <para/> /// <para/>
@ -85,18 +87,22 @@ namespace SpotifyAPI.Web.Auth
/// <param name="refreshToken">This needs to be defined if "grantType" is "refresh_token".</param> /// <param name="refreshToken">This needs to be defined if "grantType" is "refresh_token".</param>
/// <param name="currentRetries">Does not need to be defined. Used internally for retry attempt recursion.</param> /// <param name="currentRetries">Does not need to be defined. Used internally for retry attempt recursion.</param>
/// <returns>Attempts to return a full <see cref="Token"/>, but after retry attempts, may return a <see cref="Token"/> with no <see cref="Token.AccessToken"/>, or null.</returns> /// <returns>Attempts to return a full <see cref="Token"/>, but after retry attempts, may return a <see cref="Token"/> with no <see cref="Token.AccessToken"/>, or null.</returns>
async Task<Token> GetToken(string grantType, string authorizationCode = "", string refreshToken = "", int currentRetries = 0) async Task<Token> GetToken(string grantType, string authorizationCode = "", string refreshToken = "",
int currentRetries = 0)
{ {
var content = new FormUrlEncodedContent(new Dictionary<string, string> FormUrlEncodedContent content = new FormUrlEncodedContent(new Dictionary<string, string>
{ {
{ "grant_type", grantType }, {"grant_type", grantType},
{ "code", authorizationCode }, {"code", authorizationCode},
{ "refresh_token", refreshToken } {"refresh_token", refreshToken}
}); });
try try
{ {
var siteResponse = await httpClient.PostAsync(exchangeServerUri, content); HttpClientHandler handler = ProxyConfig.CreateClientHandler(ProxyConfig);
HttpClient client = new HttpClient(handler);
HttpResponseMessage siteResponse = await client.PostAsync(_exchangeServerUri, content);
Token token = JsonConvert.DeserializeObject<Token>(await siteResponse.Content.ReadAsStringAsync()); Token token = JsonConvert.DeserializeObject<Token>(await siteResponse.Content.ReadAsStringAsync());
// Don't need to check if it was null - if it is, it will resort to the catch block. // Don't need to check if it was null - if it is, it will resort to the catch block.
if (!token.HasError() && !string.IsNullOrEmpty(token.AccessToken)) if (!token.HasError() && !string.IsNullOrEmpty(token.AccessToken))
@ -104,7 +110,9 @@ namespace SpotifyAPI.Web.Auth
return token; return token;
} }
} }
catch { } catch
{
}
if (currentRetries >= MaxGetTokenRetries) if (currentRetries >= MaxGetTokenRetries)
{ {
@ -120,7 +128,8 @@ namespace SpotifyAPI.Web.Auth
} }
} }
System.Timers.Timer accessTokenExpireTimer; System.Timers.Timer _accessTokenExpireTimer;
/// <summary> /// <summary>
/// When Spotify authorization has expired. Will only trigger if <see cref="TimeAccessExpiry"/> is true. /// When Spotify authorization has expired. Will only trigger if <see cref="TimeAccessExpiry"/> is true.
/// </summary> /// </summary>
@ -134,19 +143,19 @@ namespace SpotifyAPI.Web.Auth
{ {
if (!TimeAccessExpiry) return; if (!TimeAccessExpiry) return;
if (accessTokenExpireTimer != null) if (_accessTokenExpireTimer != null)
{ {
accessTokenExpireTimer.Stop(); _accessTokenExpireTimer.Stop();
accessTokenExpireTimer.Dispose(); _accessTokenExpireTimer.Dispose();
} }
accessTokenExpireTimer = new System.Timers.Timer _accessTokenExpireTimer = new System.Timers.Timer
{ {
Enabled = true, Enabled = true,
Interval = token.ExpiresIn * 1000, Interval = token.ExpiresIn * 1000,
AutoReset = false AutoReset = false
}; };
accessTokenExpireTimer.Elapsed += (sender, e) => OnAccessTokenExpired?.Invoke(this, EventArgs.Empty); _accessTokenExpireTimer.Elapsed += (sender, e) => OnAccessTokenExpired?.Invoke(this, EventArgs.Empty);
} }
/// <summary> /// <summary>
@ -161,6 +170,7 @@ namespace SpotifyAPI.Web.Auth
{ {
SetAccessExpireTimer(token); SetAccessExpireTimer(token);
} }
return token; return token;
} }
@ -176,6 +186,7 @@ namespace SpotifyAPI.Web.Auth
{ {
SetAccessExpireTimer(token); SetAccessExpireTimer(token);
} }
return token; return token;
} }
} }
@ -204,7 +215,7 @@ namespace SpotifyAPI.Web.Auth
Code = code, Code = code,
Error = error Error = error
})); }));
return HttpContext.HtmlResponseAsync(((TokenSwapAuth)auth).HtmlResponse); return HttpContext.HtmlResponseAsync(((TokenSwapAuth) auth).HtmlResponse);
} }
} }
} }

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Net; using System.Net;
using System.Net.Http;
namespace SpotifyAPI.Web namespace SpotifyAPI.Web
{ {
@ -73,5 +74,24 @@ namespace SpotifyAPI.Web
return proxy; return proxy;
} }
public static HttpClientHandler CreateClientHandler(ProxyConfig proxyConfig = null)
{
HttpClientHandler clientHandler = new HttpClientHandler
{
PreAuthenticate = false,
UseDefaultCredentials = true,
UseProxy = false
};
if (string.IsNullOrWhiteSpace(proxyConfig?.Host)) return clientHandler;
WebProxy proxy = proxyConfig.CreateWebProxy();
clientHandler.UseProxy = true;
clientHandler.Proxy = proxy;
clientHandler.UseDefaultCredentials = proxy.UseDefaultCredentials;
clientHandler.PreAuthenticate = proxy.UseDefaultCredentials;
return clientHandler;
}
} }
} }

View File

@ -20,7 +20,7 @@ namespace SpotifyAPI.Web
public SpotifyWebClient(ProxyConfig proxyConfig = null) public SpotifyWebClient(ProxyConfig proxyConfig = null)
{ {
HttpClientHandler clientHandler = CreateClientHandler(proxyConfig); HttpClientHandler clientHandler = ProxyConfig.CreateClientHandler(proxyConfig);
_client = new HttpClient(clientHandler); _client = new HttpClient(clientHandler);
} }
@ -200,24 +200,5 @@ namespace SpotifyAPI.Web
_client.DefaultRequestHeaders.TryAddWithoutValidation(headerPair.Key, headerPair.Value); _client.DefaultRequestHeaders.TryAddWithoutValidation(headerPair.Key, headerPair.Value);
} }
} }
private static HttpClientHandler CreateClientHandler(ProxyConfig proxyConfig = null)
{
HttpClientHandler clientHandler = new HttpClientHandler
{
PreAuthenticate = false,
UseDefaultCredentials = true,
UseProxy = false
};
if (string.IsNullOrWhiteSpace(proxyConfig?.Host)) return clientHandler;
WebProxy proxy = proxyConfig.CreateWebProxy();
clientHandler.UseProxy = true;
clientHandler.Proxy = proxy;
clientHandler.UseDefaultCredentials = proxy.UseDefaultCredentials;
clientHandler.PreAuthenticate = proxy.UseDefaultCredentials;
return clientHandler;
}
} }
} }