using System; using System.Collections.Generic; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; using SpotifyAPI.Web.Http; namespace SpotifyAPI.Web { public class OAuthClient : APIClient, IOAuthClient { public OAuthClient() : this(SpotifyClientConfig.CreateDefault()) { } public OAuthClient(IAPIConnector apiConnector) : base(apiConnector) { } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062")] public OAuthClient(SpotifyClientConfig config) : base(ValidateConfig(config)) { } /// /// Requests a new token using pkce flow /// /// The request-model which contains required and optional parameters. /// The cancellation-token to allow to cancel the request. /// /// https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow-with-proof-key-for-code-exchange-pkce /// /// 1 public Task RequestToken(PKCETokenRequest request, CancellationToken cancel = default) { return RequestToken(request, API, cancel); } /// /// Refreshes a token using pkce flow /// /// The request-model which contains required and optional parameters. /// The cancellation-token to allow to cancel the request. /// /// https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow-with-proof-key-for-code-exchange-pkce /// /// 1 public Task RequestToken(PKCETokenRefreshRequest request, CancellationToken cancel = default) { return RequestToken(request, API, cancel); } /// /// Requests a new token using client_ids and client_secrets. /// If the token is expired, simply call the funtion again to get a new token /// /// The request-model which contains required and optional parameters. /// The cancellation-token to allow to cancel the request. /// /// https://developer.spotify.com/documentation/general/guides/authorization-guide/#client-credentials-flow /// /// 1 public Task RequestToken(ClientCredentialsRequest request, CancellationToken cancel = default) { return RequestToken(request, API, cancel); } /// /// Refresh an already received token via Authorization Code Auth /// /// The request-model which contains required and optional parameters. /// The cancellation-token to allow to cancel the request. /// /// https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow /// /// public Task RequestToken(AuthorizationCodeRefreshRequest request, CancellationToken cancel = default) { return RequestToken(request, API, cancel); } /// /// Request an initial token via Authorization Code Auth /// /// The request-model which contains required and optional parameters. /// The cancellation-token to allow to cancel the request. /// /// https://developer.spotify.com/documentation/general/guides/authorization-guide/#authorization-code-flow /// /// public Task RequestToken(AuthorizationCodeTokenRequest request, CancellationToken cancel = default) { return RequestToken(request, API, cancel); } /// /// Swaps out a received code with a access token using a remote server /// /// The request-model which contains required and optional parameters. /// The cancellation-token to allow to cancel the request. /// /// https://developer.spotify.com/documentation/ios/guides/token-swap-and-refresh/ /// /// public Task RequestToken(TokenSwapTokenRequest request, CancellationToken cancel = default) { return RequestToken(request, API, cancel); } /// /// Gets a refreshed access token using an already received refresh token using a remote server /// /// /// The cancellation-token to allow to cancel the request. /// /// https://developer.spotify.com/documentation/ios/guides/token-swap-and-refresh/ /// /// public Task RequestToken( TokenSwapRefreshRequest request, CancellationToken cancel = default) { return RequestToken(request, API, cancel); } public static Task RequestToken( PKCETokenRequest request, IAPIConnector apiConnector, CancellationToken cancel = default) { Ensure.ArgumentNotNull(request, nameof(request)); Ensure.ArgumentNotNull(apiConnector, nameof(apiConnector)); var form = new List> { new KeyValuePair("client_id", request.ClientId), new KeyValuePair("grant_type", "authorization_code"), new KeyValuePair("code", request.Code), new KeyValuePair("redirect_uri", request.RedirectUri.ToString()), new KeyValuePair("code_verifier", request.CodeVerifier), }; return SendOAuthRequest(apiConnector, form, null, null, cancel); } public static Task RequestToken( PKCETokenRefreshRequest request, IAPIConnector apiConnector, CancellationToken cancel = default) { Ensure.ArgumentNotNull(request, nameof(request)); Ensure.ArgumentNotNull(apiConnector, nameof(apiConnector)); var form = new List> { new KeyValuePair("client_id", request.ClientId), new KeyValuePair("grant_type", "refresh_token"), new KeyValuePair("refresh_token", request.RefreshToken), }; return SendOAuthRequest(apiConnector, form, null, null, cancel); } public static Task RequestToken( TokenSwapRefreshRequest request, IAPIConnector apiConnector, CancellationToken cancel = default ) { Ensure.ArgumentNotNull(request, nameof(request)); Ensure.ArgumentNotNull(apiConnector, nameof(apiConnector)); var form = new List> { new KeyValuePair("refresh_token", request.RefreshToken) }; #pragma warning disable CA2000 return apiConnector.Post( request.RefreshUri, null, new FormUrlEncodedContent(form), cancel ); #pragma warning restore CA2000 } public static Task RequestToken( TokenSwapTokenRequest request, IAPIConnector apiConnector, CancellationToken cancel = default ) { Ensure.ArgumentNotNull(request, nameof(request)); Ensure.ArgumentNotNull(apiConnector, nameof(apiConnector)); var form = new List> { new KeyValuePair("code", request.Code) }; #pragma warning disable CA2000 return apiConnector.Post( request.TokenUri, null, new FormUrlEncodedContent(form), cancel ); #pragma warning restore CA2000 } public static Task RequestToken( ClientCredentialsRequest request, IAPIConnector apiConnector, CancellationToken cancel = default ) { Ensure.ArgumentNotNull(request, nameof(request)); Ensure.ArgumentNotNull(apiConnector, nameof(apiConnector)); var form = new List> { new KeyValuePair("grant_type", "client_credentials") }; return SendOAuthRequest(apiConnector, form, request.ClientId, request.ClientSecret, cancel); } public static Task RequestToken( AuthorizationCodeRefreshRequest request, IAPIConnector apiConnector, CancellationToken cancel = default ) { Ensure.ArgumentNotNull(request, nameof(request)); Ensure.ArgumentNotNull(apiConnector, nameof(apiConnector)); var form = new List> { new KeyValuePair("grant_type", "refresh_token"), new KeyValuePair("refresh_token", request.RefreshToken) }; return SendOAuthRequest(apiConnector, form, request.ClientId, request.ClientSecret, cancel); } public static Task RequestToken( AuthorizationCodeTokenRequest request, IAPIConnector apiConnector, CancellationToken cancel = default ) { Ensure.ArgumentNotNull(request, nameof(request)); Ensure.ArgumentNotNull(apiConnector, nameof(apiConnector)); var form = new List> { new KeyValuePair("grant_type", "authorization_code"), new KeyValuePair("code", request.Code), new KeyValuePair("redirect_uri", request.RedirectUri.ToString()) }; return SendOAuthRequest(apiConnector, form, request.ClientId, request.ClientSecret, cancel); } private static Task SendOAuthRequest( IAPIConnector apiConnector, List> form, string? clientId, string? clientSecret, CancellationToken cancel = default) { var headers = BuildAuthHeader(clientId, clientSecret); #pragma warning disable CA2000 return apiConnector.Post(SpotifyUrls.OAuthToken, null, new FormUrlEncodedContent(form), headers, cancel); #pragma warning restore CA2000 } private static Dictionary BuildAuthHeader(string? clientId, string? clientSecret) { if (clientId == null || clientSecret == null) { return new Dictionary(); } var base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{clientId}:{clientSecret}")); return new Dictionary { { "Authorization", $"Basic {base64}"} }; } private static APIConnector ValidateConfig(SpotifyClientConfig config) { Ensure.ArgumentNotNull(config, nameof(config)); return new APIConnector( config.BaseAddress, config.Authenticator, config.JSONSerializer, config.HTTPClient, config.RetryHandler, config.HTTPLogger ); } } }