The next generation of the Teknik Services. Written in ASP.NET. https://www.teknik.io/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ConsentService.cs 7.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. using IdentityServer4.Models;
  2. using IdentityServer4.Services;
  3. using IdentityServer4.Stores;
  4. using Microsoft.Extensions.Logging;
  5. using System.Linq;
  6. using System.Threading.Tasks;
  7. using Teknik.IdentityServer.Models;
  8. using Teknik.IdentityServer.Options;
  9. using Teknik.IdentityServer.ViewModels;
  10. namespace Teknik.IdentityServer.Services
  11. {
  12. public class ConsentService
  13. {
  14. private readonly IClientStore _clientStore;
  15. private readonly IResourceStore _resourceStore;
  16. private readonly IIdentityServerInteractionService _interaction;
  17. private readonly ILogger _logger;
  18. public ConsentService(
  19. IIdentityServerInteractionService interaction,
  20. IClientStore clientStore,
  21. IResourceStore resourceStore,
  22. ILogger logger)
  23. {
  24. _interaction = interaction;
  25. _clientStore = clientStore;
  26. _resourceStore = resourceStore;
  27. _logger = logger;
  28. }
  29. public async Task<ProcessConsentResult> ProcessConsent(ConsentInputModel model)
  30. {
  31. var result = new ProcessConsentResult();
  32. ConsentResponse grantedConsent = null;
  33. // user clicked 'no' - send back the standard 'access_denied' response
  34. if (model.Button == "no")
  35. {
  36. grantedConsent = ConsentResponse.Denied;
  37. }
  38. // user clicked 'yes' - validate the data
  39. else if (model.Button == "yes" && model != null)
  40. {
  41. // if the user consented to some scope, build the response model
  42. if (model.ScopesConsented != null && model.ScopesConsented.Any())
  43. {
  44. var scopes = model.ScopesConsented;
  45. if (ConsentOptions.EnableOfflineAccess == false)
  46. {
  47. scopes = scopes.Where(x => x != IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess);
  48. }
  49. grantedConsent = new ConsentResponse
  50. {
  51. RememberConsent = model.RememberConsent,
  52. ScopesConsented = scopes.ToArray()
  53. };
  54. }
  55. else
  56. {
  57. result.ValidationError = ConsentOptions.MustChooseOneErrorMessage;
  58. }
  59. }
  60. else
  61. {
  62. result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage;
  63. }
  64. if (grantedConsent != null)
  65. {
  66. // validate return url is still valid
  67. var request = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
  68. if (request == null) return result;
  69. // communicate outcome of consent back to identityserver
  70. await _interaction.GrantConsentAsync(request, grantedConsent);
  71. // indicate that's it ok to redirect back to authorization endpoint
  72. result.RedirectUri = model.ReturnUrl;
  73. }
  74. else
  75. {
  76. // we need to redisplay the consent UI
  77. result.ViewModel = await BuildViewModelAsync(model.ReturnUrl, model);
  78. }
  79. return result;
  80. }
  81. public async Task<ConsentViewModel> BuildViewModelAsync(string returnUrl, ConsentInputModel model = null)
  82. {
  83. var request = await _interaction.GetAuthorizationContextAsync(returnUrl);
  84. if (request != null)
  85. {
  86. var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId);
  87. if (client != null)
  88. {
  89. var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested);
  90. if (resources != null && (resources.IdentityResources.Any() || resources.ApiResources.Any()))
  91. {
  92. return CreateConsentViewModel(model, returnUrl, request, client, resources);
  93. }
  94. else
  95. {
  96. _logger.LogError("No scopes matching: {0}", request.ScopesRequested.Aggregate((x, y) => x + ", " + y));
  97. }
  98. }
  99. else
  100. {
  101. _logger.LogError("Invalid client id: {0}", request.ClientId);
  102. }
  103. }
  104. else
  105. {
  106. _logger.LogError("No consent request matching request: {0}", returnUrl);
  107. }
  108. return null;
  109. }
  110. private ConsentViewModel CreateConsentViewModel(
  111. ConsentInputModel model, string returnUrl,
  112. AuthorizationRequest request,
  113. Client client, Resources resources)
  114. {
  115. var vm = new ConsentViewModel();
  116. vm.RememberConsent = model?.RememberConsent ?? true;
  117. vm.ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty<string>();
  118. vm.ReturnUrl = returnUrl;
  119. vm.ClientName = client.ClientName ?? client.ClientId;
  120. vm.ClientUrl = client.ClientUri;
  121. vm.ClientLogoUrl = client.LogoUri;
  122. vm.AllowRememberConsent = client.AllowRememberConsent;
  123. vm.IdentityScopes = resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray();
  124. vm.ResourceScopes = resources.ApiResources.SelectMany(x => x.Scopes).Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray();
  125. if (ConsentOptions.EnableOfflineAccess && resources.OfflineAccess)
  126. {
  127. vm.ResourceScopes = vm.ResourceScopes.Union(new ScopeViewModel[] {
  128. GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess) || model == null)
  129. });
  130. }
  131. return vm;
  132. }
  133. public ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check)
  134. {
  135. return new ScopeViewModel
  136. {
  137. Name = identity.Name,
  138. DisplayName = identity.DisplayName,
  139. Description = identity.Description,
  140. Emphasize = identity.Emphasize,
  141. Required = identity.Required,
  142. Checked = check || identity.Required
  143. };
  144. }
  145. public ScopeViewModel CreateScopeViewModel(Scope scope, bool check)
  146. {
  147. return new ScopeViewModel
  148. {
  149. Name = scope.Name,
  150. DisplayName = scope.DisplayName,
  151. Description = scope.Description,
  152. Emphasize = scope.Emphasize,
  153. Required = scope.Required,
  154. Checked = check || scope.Required
  155. };
  156. }
  157. private ScopeViewModel GetOfflineAccessScope(bool check)
  158. {
  159. return new ScopeViewModel
  160. {
  161. Name = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess,
  162. DisplayName = ConsentOptions.OfflineAccessDisplayName,
  163. Description = ConsentOptions.OfflineAccessDescription,
  164. Emphasize = true,
  165. Checked = check
  166. };
  167. }
  168. }
  169. }