diff --git a/source/Core/Core.csproj b/source/Core/Core.csproj index e20f24881..0289dd9f6 100644 --- a/source/Core/Core.csproj +++ b/source/Core/Core.csproj @@ -170,6 +170,7 @@ + @@ -248,6 +249,7 @@ + diff --git a/source/Core/Endpoints/Connect/TokenEndpointController.cs b/source/Core/Endpoints/Connect/TokenEndpointController.cs index 6b649ef91..70cba2544 100644 --- a/source/Core/Endpoints/Connect/TokenEndpointController.cs +++ b/source/Core/Endpoints/Connect/TokenEndpointController.cs @@ -106,11 +106,11 @@ public async Task ProcessAsync(NameValueCollection parameters if (requestResult.IsError) { - return this.TokenErrorResponse(requestResult.Error, requestResult.ErrorDescription); + return this.TokenErrorResponse(requestResult.Error, requestResult.ErrorDescription, requestResult.CustomResponseParamaters); } // return response - var response = await _generator.ProcessAsync(_requestValidator.ValidatedRequest); + var response = await _generator.ProcessAsync(_requestValidator.ValidatedRequest, requestResult.CustomResponseParamaters); return this.TokenResponse(response); } diff --git a/source/Core/Extensions/DictionaryExtensions.cs b/source/Core/Extensions/DictionaryExtensions.cs new file mode 100644 index 000000000..273817e2a --- /dev/null +++ b/source/Core/Extensions/DictionaryExtensions.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Newtonsoft.Json.Linq; + +namespace IdentityServer3.Core.Extensions +{ + internal static class DictionaryExtensions + { + public static void AddDictionary(this JObject jobject, IDictionary dictionary) + { + foreach (var item in dictionary) + { + JToken token; + if (jobject.TryGetValue(item.Key, out token)) + { + throw new Exception("Item does already exist - cannot add it via a custom entry: " + item.Key); + } + + if (item.Value.GetType().GetTypeInfo().IsClass) + { + jobject.Add(new JProperty(item.Key, JToken.FromObject(item.Value))); + } + else + { + jobject.Add(new JProperty(item.Key, item.Value)); + } + } + } + } +} diff --git a/source/Core/Extensions/ResultExtensions.cs b/source/Core/Extensions/ResultExtensions.cs index 7b8c9a397..41f17e77d 100644 --- a/source/Core/Extensions/ResultExtensions.cs +++ b/source/Core/Extensions/ResultExtensions.cs @@ -14,6 +14,7 @@ * limitations under the License. */ +using System.Collections.Generic; using IdentityServer3.Core.Models; using IdentityServer3.Core.Results; using System.Web.Http; @@ -36,5 +37,10 @@ public static IHttpActionResult TokenErrorResponse(this ApiController controller { return new TokenErrorResult(error, errorDescription); } + + public static IHttpActionResult TokenErrorResponse(this ApiController controller, string error, string errorDescription, IDictionary customResponseParamaters) + { + return new TokenErrorResult(error, errorDescription, customResponseParamaters); + } } } \ No newline at end of file diff --git a/source/Core/ResponseHandling/TokenResponseGenerator.cs b/source/Core/ResponseHandling/TokenResponseGenerator.cs index e0c34e04a..ae9b5e21f 100644 --- a/source/Core/ResponseHandling/TokenResponseGenerator.cs +++ b/source/Core/ResponseHandling/TokenResponseGenerator.cs @@ -20,6 +20,7 @@ using IdentityServer3.Core.Services; using IdentityServer3.Core.Validation; using System; +using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Threading.Tasks; @@ -46,7 +47,7 @@ public TokenResponseGenerator(ITokenService tokenService, IRefreshTokenService r _customResponseGenerator = customResponseGenerator; } - public async Task ProcessAsync(ValidatedTokenRequest request) + public async Task ProcessAsync(ValidatedTokenRequest request, IDictionary customResponseParameters = null) { Logger.Info("Creating token response"); @@ -65,6 +66,14 @@ public async Task ProcessAsync(ValidatedTokenRequest request) response = await ProcessTokenRequestAsync(request); } + if (customResponseParameters != null && customResponseParameters.Any()) + { + foreach (var customResponseParameter in customResponseParameters) + { + response.Custom.Add(customResponseParameter.Key, customResponseParameter.Value); + } + } + return await _customResponseGenerator.GenerateAsync(request, response); } @@ -144,7 +153,7 @@ private async Task ProcessRefreshTokenRequestAsync(ValidatedToken var oldAccessToken = request.RefreshToken.AccessToken; string accessTokenString; - + // if pop request, claims must be updated because we need a fresh proof token if (request.Client.UpdateAccessTokenClaimsOnRefresh || request.RequestedTokenType == RequestedTokenTypes.PoP) { @@ -202,7 +211,7 @@ private async Task> CreateAccessTokenAsync(ValidatedTokenR if (request.AuthorizationCode != null) { createRefreshToken = request.AuthorizationCode.RequestedScopes.Select(s => s.Name).Contains(Constants.StandardScopes.OfflineAccess); - + tokenRequest = new TokenCreationRequest { Subject = request.AuthorizationCode.Subject, diff --git a/source/Core/Results/TokenErrorResult.cs b/source/Core/Results/TokenErrorResult.cs index 783f6cd85..c107681f6 100644 --- a/source/Core/Results/TokenErrorResult.cs +++ b/source/Core/Results/TokenErrorResult.cs @@ -14,33 +14,53 @@ * limitations under the License. */ +using System.Collections.Generic; +using System.Linq; using IdentityServer3.Core.Logging; using Newtonsoft.Json; using System.Net; using System.Net.Http; -using System.Net.Http.Formatting; +using System.Text; using System.Threading; using System.Threading.Tasks; using System.Web.Http; +using IdentityServer3.Core.Extensions; +using IdentityServer3.Core.Services; namespace IdentityServer3.Core.Results { internal class TokenErrorResult : IHttpActionResult { - private readonly static ILog Logger = LogProvider.GetCurrentClassLogger(); - + private static readonly ILog Logger = LogProvider.GetCurrentClassLogger(); + + public IDictionary CustomResponseParamaters { get; set; } public string Error { get; internal set; } public string ErrorDescription { get; internal set; } public TokenErrorResult(string error) { Error = error; + CustomResponseParamaters = new Dictionary(); } public TokenErrorResult(string error, string errorDescription) { Error = error; ErrorDescription = errorDescription; + CustomResponseParamaters = new Dictionary(); + } + + public TokenErrorResult(string error, IDictionary customResponseParamaters) + { + Error = error; + CustomResponseParamaters = customResponseParamaters; + } + + public TokenErrorResult(string error, string errorDescription, IDictionary customResponseParamaters) + { + Error = error; + ErrorDescription = errorDescription; + CustomResponseParamaters = customResponseParamaters; } public Task ExecuteAsync(CancellationToken cancellationToken) @@ -56,9 +76,16 @@ private HttpResponseMessage Execute() error_description = ErrorDescription }; + var jObject = ObjectSerializer.ToJObject(dto); + + if (CustomResponseParamaters != null && CustomResponseParamaters.Any()) + { + jObject.AddDictionary(CustomResponseParamaters); + } + var response = new HttpResponseMessage(HttpStatusCode.BadRequest) { - Content = new ObjectContent(dto, new JsonMediaTypeFormatter()) + Content = new StringContent(jObject.ToString(Formatting.None), Encoding.UTF8, "application/json") }; Logger.Info("Returning error: " + Error); @@ -70,6 +97,6 @@ internal class ErrorDto public string error { get; set; } [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public string error_description { get; set; } - } + } } } \ No newline at end of file diff --git a/source/Core/Results/TokenResult.cs b/source/Core/Results/TokenResult.cs index 09398eaf1..7da1a428f 100644 --- a/source/Core/Results/TokenResult.cs +++ b/source/Core/Results/TokenResult.cs @@ -17,8 +17,6 @@ using IdentityServer3.Core.Logging; using IdentityServer3.Core.Models; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; using System.Linq; using System.Net; using System.Net.Http; @@ -26,18 +24,15 @@ using System.Threading; using System.Threading.Tasks; using System.Web.Http; +using IdentityServer3.Core.Extensions; +using IdentityServer3.Core.Services; namespace IdentityServer3.Core.Results { internal class TokenResult : IHttpActionResult { private readonly static ILog Logger = LogProvider.GetCurrentClassLogger(); - private readonly static JsonSerializer Serializer = new JsonSerializer - { - DefaultValueHandling = DefaultValueHandling.Ignore, - NullValueHandling = NullValueHandling.Ignore - }; - + private readonly TokenResponse _response; public TokenResult(TokenResponse response) @@ -62,33 +57,18 @@ private HttpResponseMessage Execute() alg = _response.Algorithm }; - var jobject = JObject.FromObject(dto, Serializer); + var jObject = ObjectSerializer.ToJObject(dto); // custom entries if (_response.Custom != null && _response.Custom.Any()) { - foreach (var item in _response.Custom) - { - JToken token; - if (jobject.TryGetValue(item.Key, out token)) - { - throw new Exception("Item does already exist - cannot add it via a custom entry: " + item.Key); - } - - if (item.Value.GetType().IsClass) - { - jobject.Add(new JProperty(item.Key, JToken.FromObject(item.Value))); - } - else - { - jobject.Add(new JProperty(item.Key, item.Value)); - } - } + jObject.AddDictionary(_response.Custom); } var response = new HttpResponseMessage(HttpStatusCode.OK) { - Content = new StringContent(jobject.ToString(Formatting.None), Encoding.UTF8, "application/json") + //Content = new ObjectContent(jobject, new JsonMediaTypeFormatter()) + Content = new StringContent(jObject.ToString(Formatting.None), Encoding.UTF8, "application/json") }; Logger.Info("Returning token response."); diff --git a/source/Core/Services/ObjectSerializer.cs b/source/Core/Services/ObjectSerializer.cs new file mode 100644 index 000000000..65263671a --- /dev/null +++ b/source/Core/Services/ObjectSerializer.cs @@ -0,0 +1,30 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace IdentityServer3.Core.Services +{ + internal static class ObjectSerializer + { + private static readonly JsonSerializerSettings settings = new JsonSerializerSettings + { + DefaultValueHandling = DefaultValueHandling.Ignore, + NullValueHandling = NullValueHandling.Ignore + }; + + private static readonly JsonSerializer serializer = new JsonSerializer + { + DefaultValueHandling = DefaultValueHandling.Ignore, + NullValueHandling = NullValueHandling.Ignore + }; + + public static string ToString(object o) + { + return JsonConvert.SerializeObject(o, settings); + } + + public static JObject ToJObject(object o) + { + return JObject.FromObject(o, serializer); + } + } +} diff --git a/source/Core/Validation/CustomGrantValidationResult.cs b/source/Core/Validation/CustomGrantValidationResult.cs index 66a4d3f61..a6b078297 100644 --- a/source/Core/Validation/CustomGrantValidationResult.cs +++ b/source/Core/Validation/CustomGrantValidationResult.cs @@ -27,6 +27,11 @@ namespace IdentityServer3.Core.Validation /// public class CustomGrantValidationResult : ValidationResult { + private string customerId; + private string deviceToken; + private List claims; + private Dictionary customResponseParamaters; + /// /// Gets or sets the principal which represents the result of the authentication. /// @@ -35,13 +40,24 @@ public class CustomGrantValidationResult : ValidationResult /// public ClaimsPrincipal Principal { get; set; } + /// + /// Gets or sets custom response parameters + /// + /// + /// Custom response parameters + /// + public Dictionary CustomResponseParamaters { get; set; } + /// /// Initializes a new instance of the class with an error message. /// /// The error message. - public CustomGrantValidationResult(string errorMessage) + /// Custom response parameters + public CustomGrantValidationResult(string errorMessage, + Dictionary customResponseParamaters = null) { Error = errorMessage; + CustomResponseParamaters = customResponseParamaters; } /// @@ -57,11 +73,13 @@ public CustomGrantValidationResult() /// The authentication method which describes the custom grant type. /// Additional claims that will be maintained in the principal. /// The identity provider. + /// Custom response parameters public CustomGrantValidationResult( - string subject, + string subject, string authenticationMethod, IEnumerable claims = null, - string identityProvider = Constants.BuiltInIdentityProvider) + string identityProvider = Constants.BuiltInIdentityProvider, + Dictionary customResponseParamaters = null) { var resultClaims = new List { @@ -80,6 +98,7 @@ public CustomGrantValidationResult( id.AddClaims(resultClaims.Distinct(new ClaimComparer())); Principal = new ClaimsPrincipal(id); + CustomResponseParamaters = customResponseParamaters; IsError = false; } diff --git a/source/Core/Validation/TokenRequestValidationResult.cs b/source/Core/Validation/TokenRequestValidationResult.cs index 3f7e0c33f..fd97e37a1 100644 --- a/source/Core/Validation/TokenRequestValidationResult.cs +++ b/source/Core/Validation/TokenRequestValidationResult.cs @@ -14,11 +14,15 @@ * limitations under the License. */ +using System.Collections.Generic; + namespace IdentityServer3.Core.Validation { /// /// Validation result for token requests /// public class TokenRequestValidationResult : ValidationResult - { } + { + public IDictionary CustomResponseParamaters { get; set; } + } } diff --git a/source/Core/Validation/TokenRequestValidator.cs b/source/Core/Validation/TokenRequestValidator.cs index 5be2da3aa..043cbcc9a 100644 --- a/source/Core/Validation/TokenRequestValidator.cs +++ b/source/Core/Validation/TokenRequestValidator.cs @@ -21,6 +21,7 @@ using IdentityServer3.Core.Models; using IdentityServer3.Core.Services; using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; using System.Text; @@ -160,10 +161,39 @@ async Task RunValidationAsync(Func ConcatenateCustomResponseParameters( + IDictionary standardValidator, + IDictionary customValidator) + { + if (standardValidator == null) + { + standardValidator = new Dictionary(); + } + + if (customValidator == null) + { + customValidator = new Dictionary(); + } + + foreach (var customResponseParameter in customValidator) + { + if (!standardValidator.ContainsKey(customResponseParameter.Key)) + { + standardValidator.Add(customResponseParameter.Key, customResponseParameter.Value); + } + } + + return standardValidator; + } + private async Task ValidateAuthorizationCodeRequestAsync(NameValueCollection parameters) { Logger.Info("Start validation of authorization code token request"); @@ -629,7 +659,7 @@ private async Task ValidateRefreshTokenRequestAsyn // make sure user is enabled ///////////////////////////////////////////// var principal = IdentityServerPrincipal.FromSubjectId(_validatedRequest.RefreshToken.SubjectId, refreshToken.AccessToken.Claims); - + var isActiveCtx = new IsActiveContext(principal, _validatedRequest.Client); await _users.IsActiveAsync(isActiveCtx); @@ -726,22 +756,21 @@ private async Task ValidateCustomGrantRequestAsync if (result.Error.IsPresent()) { LogError("Invalid custom grant: " + result.Error); - return Invalid(result.Error, result.ErrorDescription ?? ""); + return Invalid(result.Error, result.ErrorDescription ?? "", result.CustomResponseParamaters); } else { - LogError("Invalid custom grant."); - return Invalid(Constants.TokenErrors.InvalidGrant); + LogError("Invalid custom grant."); return Invalid(Constants.TokenErrors.InvalidGrant, string.Empty, result.CustomResponseParamaters); } } - + if (result.Principal != null) { _validatedRequest.Subject = result.Principal; } Logger.Info("Validation of custom grant token request success"); - return Valid(); + return Valid(result.CustomResponseParamaters); } private async Task ValidateRequestedScopesAsync(NameValueCollection parameters) @@ -846,7 +875,7 @@ private TokenRequestValidationResult ValidatePopParameters(NameValueCollection p _validatedRequest.ProofKeyAlgorithm = alg; } - + // key is required - for now we only support client generated keys var key = parameters.Get(Constants.TokenRequest.Key); if (key == null) @@ -867,20 +896,23 @@ private TokenRequestValidationResult ValidatePopParameters(NameValueCollection p return new TokenRequestValidationResult { IsError = false }; } - private TokenRequestValidationResult Valid() + private TokenRequestValidationResult Valid(IDictionary customResponseParameters = null) { - return new TokenRequestValidationResult + return new TokenRequestValidationResult() { - IsError = false + IsError = false, + CustomResponseParamaters = customResponseParameters }; } - private TokenRequestValidationResult Invalid(string error, string errorDescription = "") + private TokenRequestValidationResult Invalid(string error, string errorDescription = "", + IDictionary customResponseParameters = null) { var result = new TokenRequestValidationResult { IsError = true, - Error = error + Error = error, + CustomResponseParamaters = customResponseParameters }; if (errorDescription.IsPresent()) diff --git a/source/Tests/UnitTests/Core.Tests.csproj b/source/Tests/UnitTests/Core.Tests.csproj index 2b0d361e5..00b78493f 100644 --- a/source/Tests/UnitTests/Core.Tests.csproj +++ b/source/Tests/UnitTests/Core.Tests.csproj @@ -131,6 +131,7 @@ False ..\..\packages\Microsoft.AspNet.Cors.5.2.3\lib\net45\System.Web.Cors.dll + False ..\..\packages\Microsoft.AspNet.WebApi.Core.5.2.3\lib\net45\System.Web.Http.dll @@ -195,6 +196,10 @@ + + + + diff --git a/source/Tests/UnitTests/ResponseHandling/TokenErrorResultTests.cs b/source/Tests/UnitTests/ResponseHandling/TokenErrorResultTests.cs new file mode 100644 index 000000000..608732d7f --- /dev/null +++ b/source/Tests/UnitTests/ResponseHandling/TokenErrorResultTests.cs @@ -0,0 +1,156 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Script.Serialization; +using IdentityServer3.Core.Results; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace IdentityServer3.Tests.ResponseHandling +{ + public class TokenErrorResultTests + { + [Fact] + public async Task When_error_result_contains_custom_response_parameters_then_execute_return_json_with_given_parameters() + { + var customResponseParameters = new Dictionary() + { + {"customKey1", "customValue1"}, + {"customKey2", "customValue2"} + }; + + var errorResult = new TokenErrorResult("error message", "error description", customResponseParameters); + var result = await errorResult.ExecuteAsync(new CancellationToken()); + + JavaScriptSerializer jsonSerializer = new JavaScriptSerializer(); + var content = await result.Content.ReadAsStringAsync(); + var contentObject = (IDictionary)jsonSerializer.DeserializeObject(content); + + Assert.NotNull(contentObject); + Assert.True(contentObject.ContainsKey("customKey1")); + Assert.Equal("customValue1", contentObject["customKey1"]); + Assert.True(contentObject.ContainsKey("customKey2")); + Assert.Equal("customValue2", contentObject["customKey2"]); + } + + [Fact] + public async Task When_error_result_contains_custom_response_parameters_with_existing_key_then_execut_throw_exception() + { + var customResponseParameters = new Dictionary() + { + {"error", "another error value"} + }; + + var errorResult = new TokenErrorResult("error message", "error description", customResponseParameters); + + await Assert.ThrowsAsync(async () => + { + await errorResult.ExecuteAsync(new CancellationToken()); + }); + } + + [Fact] + public void When_invoking_tokenErrorResponse_with_custom_result_parameters_then_result_contain_given_parameters() + { + // Given + var customResponseParameters = new Dictionary() + { + {"customKey1", "customValue1"}, + {"customKey2", "customValue2"} + }; + + // When + var result = new TokenErrorResult("errorMessage", "description", customResponseParameters); + + // Then + Assert.NotNull(result); + Assert.Equal(2, result.CustomResponseParamaters.Count); + Assert.NotNull(result.CustomResponseParamaters.Any(x => x.Key == "customKey1" && x.Value == "customValue1")); + Assert.NotNull(result.CustomResponseParamaters.Any(x => x.Key == "customKey2" && x.Value == "customValue2")); + } + + [Fact] + public void When_invoking_tokenErrorResponse_without_custom_result_parameters_then_result_contain_empty_custom_response_parameters() + { + // When + var result = new TokenErrorResult("errorMessage", "description"); + + // Then + Assert.NotNull(result); + Assert.Empty(result.CustomResponseParamaters); + } + + [Fact] + public async Task When_invoking_execute_with_custom_parameters_then_content_contain_proper_custom_parameters() + { + // Given + var customResponseParameters = new Dictionary() + { + {"customKey1", "customValue1"}, + {"customKey2", "customValue2"} + }; + + // When + var errorToken = new TokenErrorResult("errorMessage", "description", customResponseParameters); + + var result = await errorToken.ExecuteAsync(new CancellationToken()); + var resultContent = await result.Content.ReadAsStringAsync(); + var parsedContent = JObject.Parse(resultContent); + + // Then + var customKey1 = parsedContent.GetValue("customKey1"); + Assert.Equal("customValue1", customKey1.Value()); + + var customKey2 = parsedContent.GetValue("customKey2"); + Assert.Equal("customValue2", customKey2.Value()); + } + + [Fact] + public async Task When_invoking_execute_with_custom_parameters_then_content_is_in_proper_json_format() + { + // Given + var customResponseParameters = new Dictionary() + { + {"customKey1", "customValue1"}, + {"customKey2", "customValue2"} + }; + + // When + var errorToken = new TokenErrorResult("errorMessage", "description", customResponseParameters); + + var result = await errorToken.ExecuteAsync(new CancellationToken()); + var resultContent = await result.Content.ReadAsStringAsync(); + var parsedContent = JObject.Parse(resultContent); + + // Then + Assert.NotNull(parsedContent); + } + + [Fact] + public async Task When_invoking_execute_with_null_custom_parameters_then_does_not_throw_exception() + { + // When + var errorToken = new TokenErrorResult("errorMessage", "description", null); + + var result = await errorToken.ExecuteAsync(new CancellationToken()); + var resultContent = await result.Content.ReadAsStringAsync(); + + // Then + Assert.NotNull(resultContent); + } + + [Fact] + public async Task When_invoking_execute_then_httpStatusCode_is_400() + { + // When + var errorToken = new TokenErrorResult("errorMessage", "description"); + + var result = await errorToken.ExecuteAsync(new CancellationToken()); + + // Then + Assert.Equal(HttpStatusCode.BadRequest, result.StatusCode); + } + } +} diff --git a/source/Tests/UnitTests/ResponseHandling/TokenExtensionsTests.cs b/source/Tests/UnitTests/ResponseHandling/TokenExtensionsTests.cs new file mode 100644 index 000000000..08a7ab282 --- /dev/null +++ b/source/Tests/UnitTests/ResponseHandling/TokenExtensionsTests.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using System.Web.Http; +using IdentityServer3.Core.Results; +using Xunit; +using IdentityServer3.Core.Extensions; +using IdentityServer3.Core.Models; + +namespace IdentityServer3.Tests.ResponseHandling +{ + public class TokenExtensionsTests + { + public class TestController : ApiController + { + + + } + + [Fact] + public void When_creating_token_error_response_with_error_then_result_is_instance_of_tokenErrorResult() + { + // When + var tokenResponse = new TestController().TokenErrorResponse("error"); + + // THen + Assert.NotNull(tokenResponse as TokenErrorResult); + } + + [Fact] + public void When_creating_token_error_response_with_error_then_result_contain_error() + { + // When + var tokenResponse = new TestController().TokenErrorResponse("error") as TokenErrorResult; + + // THen + Assert.Equal("error", tokenResponse.Error); + } + + [Fact] + public void When_creating_token_error_response_with_error_and_description_then_result_contain_given_data() + { + // When + var tokenResponse = new TestController().TokenErrorResponse("error", "description") as TokenErrorResult; + + // THen + Assert.Equal("error", tokenResponse.Error); + Assert.Equal("description", tokenResponse.ErrorDescription); + } + + [Fact] + public void When_creating_token_error_response_with_error_description_and_custom_parameters_then_result_contain_given_data() + { + // When + var tokenResponse = new TestController().TokenErrorResponse("error", "description", new Dictionary() + { + {"customParameter1","customValue1" } + }) as TokenErrorResult; + + // Then + Assert.Equal("error", tokenResponse.Error); + Assert.Equal("description", tokenResponse.ErrorDescription); + Assert.Equal(1, tokenResponse.CustomResponseParamaters.Count); + Assert.Equal("customValue1", tokenResponse.CustomResponseParamaters["customParameter1"]); + } + } +} diff --git a/source/Tests/UnitTests/ResponseHandling/TokenResponseGeneratorTests_CustomResponseParameters.cs b/source/Tests/UnitTests/ResponseHandling/TokenResponseGeneratorTests_CustomResponseParameters.cs new file mode 100644 index 000000000..7c8d5e162 --- /dev/null +++ b/source/Tests/UnitTests/ResponseHandling/TokenResponseGeneratorTests_CustomResponseParameters.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using IdentityServer3.Core; +using IdentityServer3.Core.Models; +using IdentityServer3.Core.ResponseHandling; +using IdentityServer3.Core.Services; +using IdentityServer3.Core.Validation; +using Moq; +using Xunit; + +namespace IdentityServer3.Tests.ResponseHandling +{ + public class TokenResponseGeneratorTests_CustomResponseParameters + { + private readonly Mock tokenServiceMock; + private readonly Mock refreshTokenServiceMock; + private readonly Mock scopeStoreMock; + private readonly Mock customResponseGeneratorMock; + private readonly TokenResponseGenerator tokenResponseGenerator; + + public TokenResponseGeneratorTests_CustomResponseParameters() + { + tokenServiceMock = new Mock(); + tokenServiceMock.Setup(x => x.CreateAccessTokenAsync(It.IsAny())) + .ReturnsAsync(new Token()); + tokenServiceMock.Setup(x => x.CreateSecurityTokenAsync(It.IsAny())) + .ReturnsAsync(string.Empty); + + refreshTokenServiceMock = new Mock(); + scopeStoreMock = new Mock(); + customResponseGeneratorMock = new Mock(); + + customResponseGeneratorMock.Setup( + x => x.GenerateAsync(It.IsAny(), It.IsAny())) + .Returns((ValidatedTokenRequest vtr, TokenResponse tr) => Task.FromResult(tr)); + + tokenResponseGenerator = new TokenResponseGenerator( + tokenServiceMock.Object, + refreshTokenServiceMock.Object, + scopeStoreMock.Object, + customResponseGeneratorMock.Object); + } + + private ValidatedTokenRequest GetRequest() + { + return new ValidatedTokenRequest() + { + GrantType = Constants.GrantTypes.Password, + ValidatedScopes = new ScopeValidator(scopeStoreMock.Object), + Client = new Client() + { + AccessTokenLifetime = 3600 + } + }; + } + + [Fact] + public async Task When_process_with_emtpy_custom_response_parameters_then_return_empty_custom_response_parameters() + { + var result = await tokenResponseGenerator.ProcessAsync(GetRequest(), new Dictionary()); + Assert.Empty(result.Custom); + } + + [Fact] + public async Task When_process_with_null_custom_response_parameters_then_return_empty_custom_response_parameter() + { + var result = await tokenResponseGenerator.ProcessAsync(GetRequest(), null); + Assert.Empty(result.Custom); + } + + [Fact] + public async Task When_process_with_not_empty_custom_response_parameters_then_return_not_empty_custom_response_parameters() + { + var result = await tokenResponseGenerator.ProcessAsync(GetRequest(), + new Dictionary() + { + {"customKey1","customValue1" }, + {"customKey2","customValue2" } + }); + Assert.NotEmpty(result.Custom); + } + + [Fact] + public async Task When_process_with_not_empty_custom_response_parameters_then_return_given_custom_response_parameters() + { + var result = await tokenResponseGenerator.ProcessAsync(GetRequest(), new Dictionary() + { + {"customKey1","customValue1" }, + {"customKey2","customValue2" } + }); + + Assert.Equal(2, result.Custom.Count); + Assert.NotNull(result.Custom.Any(x => x.Key == "customKey1" && x.Value == "customValue1")); + Assert.NotNull(result.Custom.Any(x => x.Key == "customKey2" && x.Value == "customValue2")); + } + } +} diff --git a/source/Tests/UnitTests/ResponseHandling/TokenResultTests.cs b/source/Tests/UnitTests/ResponseHandling/TokenResultTests.cs new file mode 100644 index 000000000..249d5cee4 --- /dev/null +++ b/source/Tests/UnitTests/ResponseHandling/TokenResultTests.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using System.Web.Script.Serialization; +using IdentityServer3.Core.Models; +using IdentityServer3.Core.Results; +using Xunit; + +namespace IdentityServer3.Tests.ResponseHandling +{ + public class TokenResultTests + { + [Fact] + public async Task When_creating_token_response_with_custom_response_parameters_then_result_contain_given_parameters() + { + // Given + var tokenResult = new TokenResult(new TokenResponse() + { + Custom = new Dictionary() + { + {"customParameter1", "customValue1"} + } + }); + JavaScriptSerializer jsonSerializer = new JavaScriptSerializer(); + + // When + var result = await tokenResult.ExecuteAsync(new CancellationToken()); + var content = await result.Content.ReadAsStringAsync(); + var contentObject = (IDictionary)jsonSerializer.DeserializeObject(content); + + // Then + Assert.True(contentObject.ContainsKey("customParameter1")); + Assert.Equal("customValue1", contentObject["customParameter1"]); + } + } +}