Skip to content
This repository has been archived by the owner on Sep 18, 2021. It is now read-only.

Custom response parameters for custom grants #3191

Open
wants to merge 3 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions source/Core/Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@
<Compile Include="Events\TokenService\TokenIssuedDetailsBase.cs" />
<Compile Include="Events\Authentication\LocalLoginDetails.cs" />
<Compile Include="Extensions\CookieOptionsExtensions.cs" />
<Compile Include="Extensions\DictionaryExtensions.cs" />
<Compile Include="Extensions\HashExtensions.cs" />
<Compile Include="Extensions\IClientStoreExtensions.cs" />
<Compile Include="Extensions\IContainerExtensions.cs" />
Expand Down Expand Up @@ -248,6 +249,7 @@
<Compile Include="Services\IAuthenticationSessionValidator.cs" />
<Compile Include="Services\ICustomTokenResponseGenerator.cs" />
<Compile Include="Services\ISigningKeyService.cs" />
<Compile Include="Services\ObjectSerializer.cs" />
<Compile Include="Validation\BasicAuthenticationSecretParser.cs" />
<Compile Include="Validation\IntrospectionRequestValidationResult.cs" />
<Compile Include="Validation\IntrospectionRequestValidator.cs" />
Expand Down
4 changes: 2 additions & 2 deletions source/Core/Endpoints/Connect/TokenEndpointController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,11 @@ public async Task<IHttpActionResult> 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);
}

Expand Down
31 changes: 31 additions & 0 deletions source/Core/Extensions/DictionaryExtensions.cs
Original file line number Diff line number Diff line change
@@ -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<string, object> 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));
}
}
}
}
}
6 changes: 6 additions & 0 deletions source/Core/Extensions/ResultExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* limitations under the License.
*/

using System.Collections.Generic;
using IdentityServer3.Core.Models;
using IdentityServer3.Core.Results;
using System.Web.Http;
Expand All @@ -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<string, object> customResponseParamaters)
{
return new TokenErrorResult(error, errorDescription, customResponseParamaters);
}
}
}
15 changes: 12 additions & 3 deletions source/Core/ResponseHandling/TokenResponseGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -46,7 +47,7 @@ public TokenResponseGenerator(ITokenService tokenService, IRefreshTokenService r
_customResponseGenerator = customResponseGenerator;
}

public async Task<TokenResponse> ProcessAsync(ValidatedTokenRequest request)
public async Task<TokenResponse> ProcessAsync(ValidatedTokenRequest request, IDictionary<string, object> customResponseParameters = null)
{
Logger.Info("Creating token response");

Expand All @@ -65,6 +66,14 @@ public async Task<TokenResponse> 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);
}

Expand Down Expand Up @@ -144,7 +153,7 @@ private async Task<TokenResponse> 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)
{
Expand Down Expand Up @@ -202,7 +211,7 @@ private async Task<Tuple<string, string>> 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,
Expand Down
37 changes: 32 additions & 5 deletions source/Core/Results/TokenErrorResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, object> CustomResponseParamaters { get; set; }
public string Error { get; internal set; }
public string ErrorDescription { get; internal set; }

public TokenErrorResult(string error)
{
Error = error;
CustomResponseParamaters = new Dictionary<string, object>();
}

public TokenErrorResult(string error, string errorDescription)
{
Error = error;
ErrorDescription = errorDescription;
CustomResponseParamaters = new Dictionary<string, object>();
}

public TokenErrorResult(string error, IDictionary<string, object> customResponseParamaters)
{
Error = error;
CustomResponseParamaters = customResponseParamaters;
}

public TokenErrorResult(string error, string errorDescription, IDictionary<string, object> customResponseParamaters)
{
Error = error;
ErrorDescription = errorDescription;
CustomResponseParamaters = customResponseParamaters;
}

public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
Expand All @@ -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<ErrorDto>(dto, new JsonMediaTypeFormatter())
Content = new StringContent(jObject.ToString(Formatting.None), Encoding.UTF8, "application/json")
};

Logger.Info("Returning error: " + Error);
Expand All @@ -70,6 +97,6 @@ internal class ErrorDto
public string error { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string error_description { get; set; }
}
}
}
}
34 changes: 7 additions & 27 deletions source/Core/Results/TokenResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,22 @@
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;
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 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)
Expand All @@ -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>(jobject, new JsonMediaTypeFormatter())
Content = new StringContent(jObject.ToString(Formatting.None), Encoding.UTF8, "application/json")
};

Logger.Info("Returning token response.");
Expand Down
30 changes: 30 additions & 0 deletions source/Core/Services/ObjectSerializer.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
25 changes: 22 additions & 3 deletions source/Core/Validation/CustomGrantValidationResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ namespace IdentityServer3.Core.Validation
/// </summary>
public class CustomGrantValidationResult : ValidationResult
{
private string customerId;
private string deviceToken;
private List<Claim> claims;
private Dictionary<string, object> customResponseParamaters;

/// <summary>
/// Gets or sets the principal which represents the result of the authentication.
/// </summary>
Expand All @@ -35,13 +40,24 @@ public class CustomGrantValidationResult : ValidationResult
/// </value>
public ClaimsPrincipal Principal { get; set; }

/// <summary>
/// Gets or sets custom response parameters
/// </summary>
/// <value>
/// Custom response parameters
/// </value>
public Dictionary<string, object> CustomResponseParamaters { get; set; }

/// <summary>
/// Initializes a new instance of the <see cref="CustomGrantValidationResult"/> class with an error message.
/// </summary>
/// <param name="errorMessage">The error message.</param>
public CustomGrantValidationResult(string errorMessage)
/// <param name="customResponseParamaters">Custom response parameters</param>
public CustomGrantValidationResult(string errorMessage,
Dictionary<string, object> customResponseParamaters = null)
{
Error = errorMessage;
CustomResponseParamaters = customResponseParamaters;
}

/// <summary>
Expand All @@ -57,11 +73,13 @@ public CustomGrantValidationResult()
/// <param name="authenticationMethod">The authentication method which describes the custom grant type.</param>
/// <param name="claims">Additional claims that will be maintained in the principal.</param>
/// <param name="identityProvider">The identity provider.</param>
/// <param name="customResponseParamaters">Custom response parameters</param>
public CustomGrantValidationResult(
string subject,
string subject,
string authenticationMethod,
IEnumerable<Claim> claims = null,
string identityProvider = Constants.BuiltInIdentityProvider)
string identityProvider = Constants.BuiltInIdentityProvider,
Dictionary<string, object> customResponseParamaters = null)
{
var resultClaims = new List<Claim>
{
Expand All @@ -80,6 +98,7 @@ public CustomGrantValidationResult(
id.AddClaims(resultClaims.Distinct(new ClaimComparer()));

Principal = new ClaimsPrincipal(id);
CustomResponseParamaters = customResponseParamaters;

IsError = false;
}
Expand Down
6 changes: 5 additions & 1 deletion source/Core/Validation/TokenRequestValidationResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,15 @@
* limitations under the License.
*/

using System.Collections.Generic;

namespace IdentityServer3.Core.Validation
{
/// <summary>
/// Validation result for token requests
/// </summary>
public class TokenRequestValidationResult : ValidationResult
{ }
{
public IDictionary<string, object> CustomResponseParamaters { get; set; }
}
}
Loading