diff --git a/Source/Lib/TraktApiSharp/Requests/Authentication/AuthorizationPollRequestBody.cs b/Source/Lib/TraktApiSharp/Requests/Authentication/AuthorizationPollRequestBody.cs index 15cd9bdbe..578b6a649 100644 --- a/Source/Lib/TraktApiSharp/Requests/Authentication/AuthorizationPollRequestBody.cs +++ b/Source/Lib/TraktApiSharp/Requests/Authentication/AuthorizationPollRequestBody.cs @@ -1,6 +1,7 @@ namespace TraktApiSharp.Requests.Authentication { using Core; + using Extensions; using Interfaces; using Objects.Authentication.Implementations; using System; @@ -24,11 +25,20 @@ public void Validate() if (Device == null) throw new ArgumentNullException(nameof(Device)); - if (string.IsNullOrEmpty(ClientId)) - throw new ArgumentNullException(nameof(ClientId)); + if (Device.IsExpiredUnused) + throw new ArgumentException("device code expired unused", nameof(Device)); - if (string.IsNullOrEmpty(ClientSecret)) - throw new ArgumentNullException(nameof(ClientSecret)); + if (!Device.IsValid) + throw new ArgumentException("device not valid", nameof(Device)); + + if (Device.DeviceCode.ContainsSpace()) + throw new ArgumentException("device code not valid", nameof(Device.DeviceCode)); + + if (string.IsNullOrEmpty(ClientId) || ClientId.ContainsSpace()) + throw new ArgumentException("client id not valid", nameof(ClientId)); + + if (string.IsNullOrEmpty(ClientSecret) || ClientSecret.ContainsSpace()) + throw new ArgumentException("client secret not valid", nameof(ClientSecret)); } } } diff --git a/Source/Lib/TraktApiSharp/Requests/Authentication/AuthorizationRefreshRequestBody.cs b/Source/Lib/TraktApiSharp/Requests/Authentication/AuthorizationRefreshRequestBody.cs index 77d189900..e26ce303a 100644 --- a/Source/Lib/TraktApiSharp/Requests/Authentication/AuthorizationRefreshRequestBody.cs +++ b/Source/Lib/TraktApiSharp/Requests/Authentication/AuthorizationRefreshRequestBody.cs @@ -2,6 +2,7 @@ { using Core; using Enums; + using Extensions; using Interfaces; using System; using System.Net.Http; @@ -25,17 +26,17 @@ internal sealed class AuthorizationRefreshRequestBody : IRequestBody public void Validate() { - if (string.IsNullOrEmpty(RefreshToken)) - throw new ArgumentNullException(nameof(RefreshToken)); + if (string.IsNullOrEmpty(RefreshToken) || RefreshToken.ContainsSpace()) + throw new ArgumentException("refresh token not valid", nameof(RefreshToken)); - if (string.IsNullOrEmpty(ClientId)) - throw new ArgumentNullException(nameof(ClientId)); + if (string.IsNullOrEmpty(ClientId) || ClientId.ContainsSpace()) + throw new ArgumentException("client id not valid", nameof(ClientId)); - if (string.IsNullOrEmpty(ClientSecret)) - throw new ArgumentNullException(nameof(ClientSecret)); + if (string.IsNullOrEmpty(ClientSecret) || ClientSecret.ContainsSpace()) + throw new ArgumentException("client secret not valid", nameof(ClientSecret)); - if (string.IsNullOrEmpty(RedirectUri)) - throw new ArgumentNullException(nameof(RedirectUri)); + if (string.IsNullOrEmpty(RedirectUri) || RedirectUri.ContainsSpace()) + throw new ArgumentException("redirect uri not valid", nameof(RedirectUri)); } } } diff --git a/Source/Lib/TraktApiSharp/Requests/Authentication/AuthorizationRequestBody.cs b/Source/Lib/TraktApiSharp/Requests/Authentication/AuthorizationRequestBody.cs index 5427ad535..4bcd49359 100644 --- a/Source/Lib/TraktApiSharp/Requests/Authentication/AuthorizationRequestBody.cs +++ b/Source/Lib/TraktApiSharp/Requests/Authentication/AuthorizationRequestBody.cs @@ -2,6 +2,7 @@ { using Core; using Enums; + using Extensions; using Interfaces; using System; using System.Net.Http; @@ -25,17 +26,17 @@ internal sealed class AuthorizationRequestBody : IRequestBody public void Validate() { - if (string.IsNullOrEmpty(Code)) - throw new ArgumentNullException(nameof(Code)); + if (string.IsNullOrEmpty(Code) || Code.ContainsSpace()) + throw new ArgumentException("code not valid", nameof(Code)); - if (string.IsNullOrEmpty(ClientId)) - throw new ArgumentNullException(nameof(ClientId)); + if (string.IsNullOrEmpty(ClientId) || ClientId.ContainsSpace()) + throw new ArgumentException("client id not valid", nameof(ClientId)); - if (string.IsNullOrEmpty(ClientSecret)) - throw new ArgumentNullException(nameof(ClientSecret)); + if (string.IsNullOrEmpty(ClientSecret) || ClientSecret.ContainsSpace()) + throw new ArgumentException("client secret not valid", nameof(ClientSecret)); - if (string.IsNullOrEmpty(RedirectUri)) - throw new ArgumentNullException(nameof(RedirectUri)); + if (string.IsNullOrEmpty(RedirectUri) || RedirectUri.ContainsSpace()) + throw new ArgumentException("redirect uri not valid", nameof(RedirectUri)); } } } diff --git a/Source/Lib/TraktApiSharp/Requests/Authentication/AuthorizationRevokeRequestBody.cs b/Source/Lib/TraktApiSharp/Requests/Authentication/AuthorizationRevokeRequestBody.cs index 550b6ccac..58f4f132a 100644 --- a/Source/Lib/TraktApiSharp/Requests/Authentication/AuthorizationRevokeRequestBody.cs +++ b/Source/Lib/TraktApiSharp/Requests/Authentication/AuthorizationRevokeRequestBody.cs @@ -1,6 +1,7 @@ namespace TraktApiSharp.Requests.Authentication { using Core; + using Extensions; using Interfaces; using System; using System.Net.Http; @@ -16,8 +17,8 @@ internal sealed class AuthorizationRevokeRequestBody : IRequestBody public void Validate() { - if (string.IsNullOrEmpty(AccessToken)) - throw new ArgumentNullException(nameof(AccessToken)); + if (string.IsNullOrEmpty(AccessToken) || AccessToken.ContainsSpace()) + throw new ArgumentException("access token not valid", nameof(AccessToken)); } } } diff --git a/Source/Lib/TraktApiSharp/Requests/Authentication/DeviceRequestBody.cs b/Source/Lib/TraktApiSharp/Requests/Authentication/DeviceRequestBody.cs index 39b631e46..a9a5bbfa4 100644 --- a/Source/Lib/TraktApiSharp/Requests/Authentication/DeviceRequestBody.cs +++ b/Source/Lib/TraktApiSharp/Requests/Authentication/DeviceRequestBody.cs @@ -1,6 +1,7 @@ namespace TraktApiSharp.Requests.Authentication { using Core; + using Extensions; using Interfaces; using System; using System.Net.Http; @@ -16,8 +17,8 @@ internal sealed class DeviceRequestBody : IRequestBody public void Validate() { - if (string.IsNullOrEmpty(ClientId)) - throw new ArgumentNullException(nameof(ClientId)); + if (string.IsNullOrEmpty(ClientId) || ClientId.ContainsSpace()) + throw new ArgumentException("client id not valid", nameof(ClientId)); } } } diff --git a/Source/Lib/TraktApiSharp/Requests/Handler/RequestBuilder.cs b/Source/Lib/TraktApiSharp/Requests/Handler/RequestBuilder.cs new file mode 100644 index 000000000..3bda1bc6d --- /dev/null +++ b/Source/Lib/TraktApiSharp/Requests/Handler/RequestBuilder.cs @@ -0,0 +1,131 @@ +namespace TraktApiSharp.Requests.Handler +{ + using Base; + using Exceptions; + using Interfaces; + using Interfaces.Base; + using System; + using System.Collections.Generic; + using System.Net.Http.Headers; + using UriTemplates; + + internal sealed class RequestBuilder + { + private const string AUTHENTICATION_SCHEME = "Bearer"; + + private IRequest _request; + private IRequestBody _requestBody; + private readonly TraktClient _client; + + internal RequestBuilder(TraktClient client) + { + _client = client ?? throw new ArgumentNullException(nameof(client)); + } + + internal RequestBuilder(IRequest request, TraktClient client) : this(client) + { + _request = request; + } + + internal RequestBuilder WithRequestBody(IRequestBody requestBody) + { + _requestBody = requestBody; + return this; + } + + internal RequestBuilder Reset(IRequest request) + { + _request = request; + _requestBody = null; + return this; + } + + internal ExtendedHttpRequestMessage Build() + { + ExtendedHttpRequestMessage requestMessage = CreateRequestMessage(); + AddRequestBodyContent(requestMessage); + SetRequestMessageHeadersForAuthorization(requestMessage); + return requestMessage; + } + + private ExtendedHttpRequestMessage CreateRequestMessage() + { + if (_request == null) + throw new ArgumentNullException(nameof(_request)); + + const string seasonKey = "season"; + const string episodeKey = "episode"; + + string url = BuildUrl(); + var requestMessage = new ExtendedHttpRequestMessage(_request.Method, url) { Url = url }; + + if (_request is IHasId) + { + var idRequest = _request as IHasId; + + requestMessage.ObjectId = idRequest?.Id; + requestMessage.RequestObjectType = idRequest?.RequestObjectType; + } + + IDictionary parameters = _request.GetUriPathParameters(); + + if (parameters.Count != 0) + { + if (parameters.ContainsKey(seasonKey)) + { + var strSeasonNumber = (string)parameters[seasonKey]; + + if (uint.TryParse(strSeasonNumber, out uint seasonNumber)) + requestMessage.SeasonNumber = seasonNumber; + } + + if (parameters.ContainsKey(episodeKey)) + { + var strEpisodeNumber = (string)parameters[episodeKey]; + + if (uint.TryParse(strEpisodeNumber, out uint episodeNumber)) + requestMessage.EpisodeNumber = episodeNumber; + } + } + + return requestMessage; + } + + private string BuildUrl() + { + var uriTemplate = new UriTemplate(_request.UriTemplate); + IDictionary requestUriParameters = _request.GetUriPathParameters(); + + foreach (KeyValuePair parameter in requestUriParameters) + uriTemplate.AddParameterFromKeyValuePair(parameter.Key, parameter.Value); + + string uri = uriTemplate.Resolve(); + return $"{_client.Configuration.BaseUrl}{uri}"; + } + + private void AddRequestBodyContent(ExtendedHttpRequestMessage requestMessage) + { + if (_requestBody != null) + { + requestMessage.Content = _requestBody.ToHttpContent(); + requestMessage.RequestBodyJson = _requestBody.HttpContentAsString; + } + } + + private void SetRequestMessageHeadersForAuthorization(ExtendedHttpRequestMessage requestMessage) + { + AuthorizationRequirement authorizationRequirement = _request.AuthorizationRequirement; + + if (authorizationRequirement == AuthorizationRequirement.Required) + { + if (!_client.Authentication.IsAuthorized) + throw new TraktAuthorizationException("authorization is required for this request, but the current authorization parameters are invalid"); + + requestMessage.Headers.Authorization = new AuthenticationHeaderValue(AUTHENTICATION_SCHEME, _client.Authentication.Authorization.AccessToken); + } + + if (authorizationRequirement == AuthorizationRequirement.Optional && _client.Configuration.ForceAuthorization && _client.Authentication.IsAuthorized) + requestMessage.Headers.Authorization = new AuthenticationHeaderValue(AUTHENTICATION_SCHEME, _client.Authentication.Authorization.AccessToken); + } + } +} diff --git a/Source/Lib/TraktApiSharp/Requests/Handler/RequestHandler.cs b/Source/Lib/TraktApiSharp/Requests/Handler/RequestHandler.cs index d4946bfe9..66f008ebd 100644 --- a/Source/Lib/TraktApiSharp/Requests/Handler/RequestHandler.cs +++ b/Source/Lib/TraktApiSharp/Requests/Handler/RequestHandler.cs @@ -21,7 +21,6 @@ using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; - using UriTemplates; internal sealed class RequestHandler : IRequestHandler { @@ -36,41 +35,46 @@ internal sealed class RequestHandler : IRequestHandler private const string HEADER_ENDDATE_KEY = "X-End-Date"; private const string HEADER_PRIVATE_USER_KEY = "X-Private-User"; private const string MEDIA_TYPE = "application/json"; - private const string AUTHENTICATION_SCHEME = "Bearer"; // Don't mark this field as readonly, // as it is manually set in unit tests internal static HttpClient s_httpClient; private readonly TraktClient _client; + private readonly RequestBuilder _requestBuilder; internal RequestHandler(TraktClient client) { _client = client; + _requestBuilder = new RequestBuilder(_client); } public Task ExecuteNoContentRequestAsync(IRequest request, CancellationToken cancellationToken = default(CancellationToken)) { PreExecuteRequest(request); - return QueryNoContentAsync(SetupRequestMessage(request), cancellationToken); + ExtendedHttpRequestMessage requestMessage = _requestBuilder.Reset(request).Build(); + return QueryNoContentAsync(requestMessage, cancellationToken); } public Task> ExecuteSingleItemRequestAsync(IRequest request, CancellationToken cancellationToken = default(CancellationToken)) { PreExecuteRequest(request); - return QuerySingleItemAsync(SetupRequestMessage(request), false, cancellationToken); + ExtendedHttpRequestMessage requestMessage = _requestBuilder.Reset(request).Build(); + return QuerySingleItemAsync(requestMessage, false, cancellationToken); } public Task> ExecuteListRequestAsync(IRequest request, CancellationToken cancellationToken = default(CancellationToken)) { PreExecuteRequest(request); - return QueryListAsync(SetupRequestMessage(request), cancellationToken); + ExtendedHttpRequestMessage requestMessage = _requestBuilder.Reset(request).Build(); + return QueryListAsync(requestMessage, cancellationToken); } public Task> ExecutePagedRequestAsync(IRequest request, CancellationToken cancellationToken = default(CancellationToken)) { PreExecuteRequest(request); - return QueryPagedListAsync(SetupRequestMessage(request), cancellationToken); + ExtendedHttpRequestMessage requestMessage = _requestBuilder.Reset(request).Build(); + return QueryPagedListAsync(requestMessage, cancellationToken); } // post requests @@ -78,26 +82,30 @@ internal RequestHandler(TraktClient client) public Task ExecuteNoContentRequestAsync(IPostRequest request, CancellationToken cancellationToken = default(CancellationToken)) where TRequestBodyType : IRequestBody { PreExecuteRequest(request); - return QueryNoContentAsync(SetupRequestMessage(request), cancellationToken); + ExtendedHttpRequestMessage requestMessage = _requestBuilder.Reset(request).WithRequestBody(request.RequestBody).Build(); + return QueryNoContentAsync(requestMessage, cancellationToken); } public Task> ExecuteSingleItemRequestAsync(IPostRequest request, CancellationToken cancellationToken = default(CancellationToken)) where TRequestBodyType : IRequestBody { PreExecuteRequest(request); + ExtendedHttpRequestMessage requestMessage = _requestBuilder.Reset(request).WithRequestBody(request.RequestBody).Build(); var isCheckinRequest = request is CheckinRequest; - return QuerySingleItemAsync(SetupRequestMessage(request), isCheckinRequest, cancellationToken); + return QuerySingleItemAsync(requestMessage, isCheckinRequest, cancellationToken); } public Task> ExecuteListRequestAsync(IPostRequest request, CancellationToken cancellationToken = default(CancellationToken)) where TRequestBodyType : IRequestBody { PreExecuteRequest(request); - return QueryListAsync(SetupRequestMessage(request), cancellationToken); + ExtendedHttpRequestMessage requestMessage = _requestBuilder.Reset(request).WithRequestBody(request.RequestBody).Build(); + return QueryListAsync(requestMessage, cancellationToken); } public Task> ExecutePagedRequestAsync(IPostRequest request, CancellationToken cancellationToken = default(CancellationToken)) where TRequestBodyType : IRequestBody { PreExecuteRequest(request); - return QueryPagedListAsync(SetupRequestMessage(request), cancellationToken); + ExtendedHttpRequestMessage requestMessage = _requestBuilder.Reset(request).WithRequestBody(request.RequestBody).Build(); + return QueryPagedListAsync(requestMessage, cancellationToken); } // put requests @@ -105,25 +113,29 @@ internal RequestHandler(TraktClient client) public Task ExecuteNoContentRequestAsync(IPutRequest request, CancellationToken cancellationToken = default(CancellationToken)) where TRequestBodyType : IRequestBody { PreExecuteRequest(request); - return QueryNoContentAsync(SetupRequestMessage(request), cancellationToken); + ExtendedHttpRequestMessage requestMessage = _requestBuilder.Reset(request).WithRequestBody(request.RequestBody).Build(); + return QueryNoContentAsync(requestMessage, cancellationToken); } public Task> ExecuteSingleItemRequestAsync(IPutRequest request, CancellationToken cancellationToken = default(CancellationToken)) where TRequestBodyType : IRequestBody { PreExecuteRequest(request); - return QuerySingleItemAsync(SetupRequestMessage(request), false, cancellationToken); + ExtendedHttpRequestMessage requestMessage = _requestBuilder.Reset(request).WithRequestBody(request.RequestBody).Build(); + return QuerySingleItemAsync(requestMessage, false, cancellationToken); } public Task> ExecuteListRequestAsync(IPutRequest request, CancellationToken cancellationToken = default(CancellationToken)) where TRequestBodyType : IRequestBody { PreExecuteRequest(request); - return QueryListAsync(SetupRequestMessage(request), cancellationToken); + ExtendedHttpRequestMessage requestMessage = _requestBuilder.Reset(request).WithRequestBody(request.RequestBody).Build(); + return QueryListAsync(requestMessage, cancellationToken); } public Task> ExecutePagedRequestAsync(IPutRequest request, CancellationToken cancellationToken = default(CancellationToken)) where TRequestBodyType : IRequestBody { PreExecuteRequest(request); - return QueryPagedListAsync(SetupRequestMessage(request), cancellationToken); + ExtendedHttpRequestMessage requestMessage = _requestBuilder.Reset(request).WithRequestBody(request.RequestBody).Build(); + return QueryPagedListAsync(requestMessage, cancellationToken); } // query response helper methods @@ -317,106 +329,6 @@ private void SetupHttpClient() SetDefaultRequestHeaders(s_httpClient); } - private string BuildUrl(IRequest request) - { - var uriTemplate = new UriTemplate(request.UriTemplate); - IDictionary requestUriParameters = request.GetUriPathParameters(); - - foreach (KeyValuePair parameter in requestUriParameters) - uriTemplate.AddParameterFromKeyValuePair(parameter.Key, parameter.Value); - - string uri = uriTemplate.Resolve(); - return $"{_client.Configuration.BaseUrl}{uri}"; - } - - private ExtendedHttpRequestMessage SetupRequestMessage(IRequest request) - { - ExtendedHttpRequestMessage requestMessage = CreateRequestMessage(request); - SetRequestMessageHeadersForAuthorization(requestMessage, request.AuthorizationRequirement); - return requestMessage; - } - - private ExtendedHttpRequestMessage SetupRequestMessage(IPostRequest request) where TRequestBodyType : IRequestBody - { - ExtendedHttpRequestMessage requestMessage = CreateRequestMessage(request); - AddRequestBodyContent(requestMessage, request); - SetRequestMessageHeadersForAuthorization(requestMessage, request.AuthorizationRequirement); - return requestMessage; - } - - private ExtendedHttpRequestMessage SetupRequestMessage(IPostRequest request) where TRequestBodyType : IRequestBody - { - ExtendedHttpRequestMessage requestMessage = CreateRequestMessage(request); - AddRequestBodyContent(requestMessage, request); - SetRequestMessageHeadersForAuthorization(requestMessage, request.AuthorizationRequirement); - return requestMessage; - } - - private ExtendedHttpRequestMessage SetupRequestMessage(IPutRequest request) where TRequestBodyType : IRequestBody - { - ExtendedHttpRequestMessage requestMessage = CreateRequestMessage(request); - AddRequestBodyContent(requestMessage, request); - SetRequestMessageHeadersForAuthorization(requestMessage, request.AuthorizationRequirement); - return requestMessage; - } - - private ExtendedHttpRequestMessage SetupRequestMessage(IPutRequest request) where TRequestBodyType : IRequestBody - { - ExtendedHttpRequestMessage requestMessage = CreateRequestMessage(request); - AddRequestBodyContent(requestMessage, request); - SetRequestMessageHeadersForAuthorization(requestMessage, request.AuthorizationRequirement); - return requestMessage; - } - - private ExtendedHttpRequestMessage CreateRequestMessage(IRequest request) - { - const string seasonKey = "season"; - const string episodeKey = "episode"; - - string url = BuildUrl(request); - var requestMessage = new ExtendedHttpRequestMessage(request.Method, url) { Url = url }; - - if (request is IHasId) - { - var idRequest = request as IHasId; - - requestMessage.ObjectId = idRequest?.Id; - requestMessage.RequestObjectType = idRequest?.RequestObjectType; - } - - IDictionary parameters = request.GetUriPathParameters(); - - if (parameters.Count != 0) - { - if (parameters.ContainsKey(seasonKey)) - { - var strSeasonNumber = (string)parameters[seasonKey]; - - if (uint.TryParse(strSeasonNumber, out uint seasonNumber)) - requestMessage.SeasonNumber = seasonNumber; - } - - if (parameters.ContainsKey(episodeKey)) - { - var strEpisodeNumber = (string)parameters[episodeKey]; - - if (uint.TryParse(strEpisodeNumber, out uint episodeNumber)) - requestMessage.EpisodeNumber = episodeNumber; - } - } - - return requestMessage; - } - - private void AddRequestBodyContent(ExtendedHttpRequestMessage requestMessage, IHasRequestBody request) where TRequestBodyType : IRequestBody - { - if (requestMessage == null) - throw new ArgumentNullException(nameof(requestMessage)); - - requestMessage.Content = request.RequestBody.ToHttpContent(); - requestMessage.RequestBodyJson = request.RequestBody.HttpContentAsString; - } - private void SetDefaultRequestHeaders(HttpClient httpClient) { var appJsonHeader = new MediaTypeWithQualityHeaderValue(MEDIA_TYPE); @@ -431,23 +343,6 @@ private void SetDefaultRequestHeaders(HttpClient httpClient) httpClient.DefaultRequestHeaders.Accept.Add(appJsonHeader); } - private void SetRequestMessageHeadersForAuthorization(ExtendedHttpRequestMessage requestMessage, AuthorizationRequirement authorizationRequirement) - { - if (requestMessage == null) - throw new ArgumentNullException(nameof(requestMessage)); - - if (authorizationRequirement == AuthorizationRequirement.Required) - { - if (!_client.Authentication.IsAuthorized) - throw new TraktAuthorizationException("authorization is required for this request, but the current authorization parameters are invalid"); - - requestMessage.Headers.Authorization = new AuthenticationHeaderValue(AUTHENTICATION_SCHEME, _client.Authentication.Authorization.AccessToken); - } - - if (authorizationRequirement == AuthorizationRequirement.Optional && _client.Configuration.ForceAuthorization && _client.Authentication.IsAuthorized) - requestMessage.Headers.Authorization = new AuthenticationHeaderValue(AUTHENTICATION_SCHEME, _client.Authentication.Authorization.AccessToken); - } - private void ParseResponseHeaderValues(ITraktResponseHeaders headerResults, HttpResponseHeaders responseHeaders) { if (responseHeaders.TryGetValues(HEADER_PAGINATION_PAGE_KEY, out IEnumerable values)) diff --git a/Source/Lib/TraktApiSharp/TraktApiSharp.csproj b/Source/Lib/TraktApiSharp/TraktApiSharp.csproj index 72b4973f4..50fd75ba7 100644 --- a/Source/Lib/TraktApiSharp/TraktApiSharp.csproj +++ b/Source/Lib/TraktApiSharp/TraktApiSharp.csproj @@ -636,6 +636,7 @@ +