Added first OAuth request: client credentials

This commit is contained in:
Jonas Dellinger 2020-05-13 23:49:54 +02:00
parent fee995f984
commit 9b8a4cd2c9
16 changed files with 140 additions and 31 deletions

View File

@ -1,7 +1,7 @@
{
"model-class": {
"class-model": {
"scope": "csharp",
"prefix": "model-class",
"prefix": "class-model",
"body": [
"namespace SpotifyAPI.Web",
"{",
@ -15,9 +15,9 @@
],
"description": "Creates a new model"
},
"request-class": {
"class-request": {
"scope": "csharp",
"prefix": "request-class",
"prefix": "class-request",
"body": [
"namespace SpotifyAPI.Web",
"{",

View File

@ -37,9 +37,9 @@ namespace SpotifyAPI.Web
Assert.AreEqual(null, defaultConfig.HTTPLogger);
Assert.AreEqual(null, defaultConfig.RetryHandler);
Assert.IsInstanceOf(typeof(TokenHeaderAuthenticator), defaultConfig.Authenticator);
Assert.IsInstanceOf(typeof(TokenAuthenticator), defaultConfig.Authenticator);
var tokenHeaderAuth = defaultConfig.Authenticator as TokenHeaderAuthenticator;
var tokenHeaderAuth = defaultConfig.Authenticator as TokenAuthenticator;
Assert.AreEqual(token, tokenHeaderAuth.Token);
Assert.AreEqual(tokenType, tokenHeaderAuth.TokenType);
}
@ -51,7 +51,7 @@ namespace SpotifyAPI.Web
var defaultConfig = SpotifyClientConfig.CreateDefault();
var tokenConfig = defaultConfig.WithToken(token);
Assert.AreEqual(token, (tokenConfig.Authenticator as TokenHeaderAuthenticator).Token);
Assert.AreEqual(token, (tokenConfig.Authenticator as TokenAuthenticator).Token);
Assert.AreNotEqual(defaultConfig, tokenConfig);
Assert.AreEqual(null, defaultConfig.Authenticator);
}

View File

@ -46,7 +46,7 @@ namespace SpotifyAPI.Web.Tests
);
await apiConnector.SendAPIRequest<string>(new Uri("/me", UriKind.Relative), HttpMethod.Get).ConfigureAwait(false);
authenticator.Verify(a => a.Apply(It.IsAny<IRequest>()), Times.Once);
authenticator.Verify(a => a.Apply(It.IsAny<IRequest>(), It.IsAny<IAPIConnector>()), Times.Once);
httpClient.Verify(h => h.DoRequest(It.IsAny<IRequest>()), Times.Once);
serializer.Verify(s => s.DeserializeResponse<string>(response.Object), Times.Once);
}
@ -89,7 +89,7 @@ namespace SpotifyAPI.Web.Tests
await apiConnector.SendAPIRequest<string>(new Uri("/me", UriKind.Relative), HttpMethod.Get).ConfigureAwait(false);
serializer.Verify(s => s.SerializeRequest(It.IsAny<IRequest>()), Times.Once);
authenticator.Verify(a => a.Apply(It.IsAny<IRequest>()), Times.Exactly(2));
authenticator.Verify(a => a.Apply(It.IsAny<IRequest>(), It.IsAny<IAPIConnector>()), Times.Exactly(2));
httpClient.Verify(h => h.DoRequest(It.IsAny<IRequest>()), Times.Exactly(2));
serializer.Verify(s => s.DeserializeResponse<string>(response.Object), Times.Once);
}

View File

@ -6,16 +6,18 @@ using SpotifyAPI.Web.Http;
namespace SpotifyAPI.Web.Tests
{
[TestFixture]
public class TokenHeaderAuthenticatorTest
public class TokenAuthenticatorTest
{
[Test]
public void Apply_AddsCorrectHeader()
{
var authenticator = new TokenHeaderAuthenticator("MyToken", "Bearer");
var authenticator = new TokenAuthenticator("MyToken", "Bearer");
var request = new Mock<IRequest>();
var apiConnector = new Mock<IAPIConnector>();
request.SetupGet(r => r.Headers).Returns(new Dictionary<string, string>());
authenticator.Apply(request.Object);
authenticator.Apply(request.Object, apiConnector.Object);
Assert.AreEqual(request.Object.Headers["Authorization"], "Bearer MyToken");
}
}

View File

@ -11,6 +11,6 @@ namespace SpotifyAPI.Web
API = apiConnector;
}
public IAPIConnector API { get; set; }
protected IAPIConnector API { get; set; }
}
}

View File

@ -0,0 +1,9 @@
using System.Threading.Tasks;
namespace SpotifyAPI.Web
{
public interface IOAuthClient
{
Task<TokenResponse> RequestToken(ClientCredentialsRequest request);
}
}

View File

@ -0,0 +1,49 @@
using System.Text;
using System;
using System.Net.Http;
using System.Collections.Generic;
using System.Threading.Tasks;
using SpotifyAPI.Web.Http;
namespace SpotifyAPI.Web
{
public class OAuthClient : APIClient, IOAuthClient
{
public OAuthClient(IAPIConnector apiConnector) : base(apiConnector) { }
public OAuthClient() : this(SpotifyClientConfig.CreateDefault()) { }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062")]
public OAuthClient(SpotifyClientConfig config) : base(ValidateConfig(config)) { }
public Task<TokenResponse> RequestToken(ClientCredentialsRequest request)
{
Ensure.ArgumentNotNull(request, nameof(request));
var form = new List<KeyValuePair<string, string>>
{
new KeyValuePair<string, string>("grant_type", "client_credentials")
};
var base64 = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{request.ClientId}:{request.ClientSecret}"));
var headers = new Dictionary<string, string>
{
{ "Authorization", $"Basic {base64}"}
};
return API.Post<TokenResponse>(SpotifyUrls.OAuthToken, null, new FormUrlEncodedContent(form), headers);
}
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
);
}
}
}

View File

@ -49,7 +49,7 @@ namespace SpotifyAPI.Web
return new SpotifyClientConfig(
BaseAddress,
new TokenHeaderAuthenticator(token, tokenType),
new TokenAuthenticator(token, tokenType),
JSONSerializer,
HTTPClient,
RetryHandler,
@ -147,7 +147,7 @@ namespace SpotifyAPI.Web
public static SpotifyClientConfig CreateDefault(string token, string tokenType = "Bearer")
{
return CreateDefault().WithAuthenticator(new TokenHeaderAuthenticator(token, tokenType));
return CreateDefault().WithAuthenticator(new TokenAuthenticator(token, tokenType));
}
public static SpotifyClientConfig CreateDefault()

View File

@ -106,6 +106,13 @@ namespace SpotifyAPI.Web.Http
return SendAPIRequest<T>(uri, HttpMethod.Post, parameters, body);
}
public Task<T> Post<T>(Uri uri, IDictionary<string, string> parameters, object body, Dictionary<string, string> headers)
{
Ensure.ArgumentNotNull(uri, nameof(uri));
return SendAPIRequest<T>(uri, HttpMethod.Post, parameters, body, headers);
}
public async Task<HttpStatusCode> Post(Uri uri, IDictionary<string, string> parameters, object body)
{
Ensure.ArgumentNotNull(uri, nameof(uri));
@ -160,13 +167,14 @@ namespace SpotifyAPI.Web.Http
Uri uri,
HttpMethod method,
IDictionary<string, string> parameters,
object body
object body,
IDictionary<string, string> headers
)
{
Ensure.ArgumentNotNull(uri, nameof(uri));
Ensure.ArgumentNotNull(method, nameof(method));
return new Request(new Dictionary<string, string>(), parameters)
return new Request(headers ?? new Dictionary<string, string>(), parameters ?? new Dictionary<string, string>())
{
BaseAddress = _baseAddress,
Endpoint = uri,
@ -184,7 +192,10 @@ namespace SpotifyAPI.Web.Http
private async Task<IResponse> DoRequest(IRequest request)
{
await _authenticator.Apply(request).ConfigureAwait(false);
if (_authenticator != null)
{
await _authenticator.Apply(request, this).ConfigureAwait(false);
}
_httpLogger?.OnRequest(request);
IResponse response = await _httpClient.DoRequest(request).ConfigureAwait(false);
_httpLogger?.OnResponse(response);
@ -192,7 +203,10 @@ namespace SpotifyAPI.Web.Http
{
response = await _retryHandler.HandleRetry(request, response, async (newRequest) =>
{
await _authenticator.Apply(newRequest).ConfigureAwait(false);
if (_authenticator != null)
{
await _authenticator.Apply(request, this).ConfigureAwait(false);
}
var newResponse = await _httpClient.DoRequest(request).ConfigureAwait(false);
_httpLogger?.OnResponse(newResponse);
return newResponse;
@ -206,10 +220,11 @@ namespace SpotifyAPI.Web.Http
Uri uri,
HttpMethod method,
IDictionary<string, string> parameters = null,
object body = null
object body = null,
IDictionary<string, string> headers = null
)
{
var request = CreateRequest(uri, method, parameters, body);
var request = CreateRequest(uri, method, parameters, body, headers);
return DoRequest(request);
}
@ -217,10 +232,11 @@ namespace SpotifyAPI.Web.Http
Uri uri,
HttpMethod method,
IDictionary<string, string> parameters = null,
object body = null
object body = null,
IDictionary<string, string> headers = null
)
{
var request = CreateRequest(uri, method, parameters, body);
var request = CreateRequest(uri, method, parameters, body, headers);
IAPIResponse<T> apiResponse = await DoSerializedRequest<T>(request).ConfigureAwait(false);
return apiResponse.Body;
}
@ -229,10 +245,11 @@ namespace SpotifyAPI.Web.Http
Uri uri,
HttpMethod method,
IDictionary<string, string> parameters = null,
object body = null
object body = null,
IDictionary<string, string> headers = null
)
{
var request = CreateRequest(uri, method, parameters, body);
var request = CreateRequest(uri, method, parameters, body, headers);
var response = await DoSerializedRequest<object>(request).ConfigureAwait(false);
return response.Response;
}

View File

@ -2,9 +2,9 @@ using System.Threading.Tasks;
namespace SpotifyAPI.Web.Http
{
public class TokenHeaderAuthenticator : IAuthenticator
public class TokenAuthenticator : IAuthenticator
{
public TokenHeaderAuthenticator(string token, string tokenType)
public TokenAuthenticator(string token, string tokenType)
{
Token = token;
TokenType = tokenType;
@ -14,7 +14,7 @@ namespace SpotifyAPI.Web.Http
public string TokenType { get; set; }
public Task Apply(IRequest request)
public Task Apply(IRequest request, IAPIConnector apiConnector)
{
Ensure.ArgumentNotNull(request, nameof(request));

View File

@ -24,6 +24,7 @@ namespace SpotifyAPI.Web.Http
Task<T> Post<T>(Uri uri);
Task<T> Post<T>(Uri uri, IDictionary<string, string> parameters);
Task<T> Post<T>(Uri uri, IDictionary<string, string> parameters, object body);
Task<T> Post<T>(Uri uri, IDictionary<string, string> parameters, object body, Dictionary<string, string> headers);
Task<HttpStatusCode> Post(Uri uri, IDictionary<string, string> parameters, object body);
Task<T> Put<T>(Uri uri);
@ -37,7 +38,11 @@ namespace SpotifyAPI.Web.Http
Task<T> Delete<T>(Uri uri, IDictionary<string, string> parameters, object body);
Task<HttpStatusCode> Delete(Uri uri, IDictionary<string, string> parameters, object body);
Task<T> SendAPIRequest<T>(Uri uri, HttpMethod method, IDictionary<string, string> parameters = null, object body = null);
Task<T> SendAPIRequest<T>(
Uri uri, HttpMethod method,
IDictionary<string, string> parameters = null,
object body = null,
IDictionary<string, string> headers = null);
void SetRequestTimeout(TimeSpan timeout);
}

View File

@ -4,6 +4,6 @@ namespace SpotifyAPI.Web.Http
{
public interface IAuthenticator
{
Task Apply(IRequest request);
Task Apply(IRequest request, IAPIConnector apiConnector);
}
}

View File

@ -0,0 +1,16 @@
namespace SpotifyAPI.Web
{
public class ClientCredentialsRequest
{
public ClientCredentialsRequest(string clientId, string clientSecret)
{
Ensure.ArgumentNotNullOrEmptyString(clientId, nameof(clientId));
Ensure.ArgumentNotNullOrEmptyString(clientSecret, nameof(clientSecret));
ClientId = clientId;
ClientSecret = clientSecret;
}
public string ClientId { get; }
public string ClientSecret { get; }
}
}

View File

@ -0,0 +1,9 @@
namespace SpotifyAPI.Web
{
public class TokenResponse
{
public string AccessToken { get; set; }
public string TokenType { get; set; }
public int ExpiresIn { get; set; }
}
}

View File

@ -2,7 +2,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.1</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<!-- <GenerateDocumentationFile>true</GenerateDocumentationFile> -->
<PackageId>SpotifyAPI.Web</PackageId>
<Title>SpotifyAPI.Web</Title>
<Authors>Jonas Dellinger</Authors>

View File

@ -7,6 +7,8 @@ namespace SpotifyAPI.Web
public static readonly Uri APIV1 = new Uri("https://api.spotify.com/v1/");
public static readonly Uri OAuthToken = new Uri("https://accounts.spotify.com/api/token");
public static Uri Me() => EUri($"me");
public static Uri User(string userId) => EUri($"users/{userId}");