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"]);
+ }
+ }
+}