From f9d2bad69288543a89bb1f9f6eb334d293a00061 Mon Sep 17 00:00:00 2001 From: Radu Terec Date: Fri, 21 Oct 2022 16:56:35 +0200 Subject: [PATCH 01/11] Referenced Duende IdentityServer and adjusted namespaces. Claims are now strings in Duende, so I removed the obsolete call to `ClaimConverter` in `RedisUserStore.cs`. Upgraded projects to .net 6 Replaced Newtonsoft with System.Text.Json --- Api.Client/Api.Client.csproj | 3 ++- .../IdentityServer.LdapExtension.Unit.csproj | 12 ++++----- .../Extensions/LdapUserProfileService.cs | 6 ++--- .../LdapUserResourceOwnerPasswordValidator.cs | 2 +- .../IdentityServer.LdapExtension.csproj | 14 +++++----- .../LdapUserProfileService.cs | 12 ++++----- .../Sinks/CustomUserLogoutSuccessEventSink.cs | 19 ++++---------- .../UserStore/InMemoryUserStore.cs | 5 ++-- .../UserStore/RedisUserStore.cs | 22 ++++++---------- MvcVueClient/MvcVueClient.csproj | 3 ++- README.md | 3 ++- Sample/Api/Api.csproj | 5 ++-- Sample/Client/MvcClient.csproj | 3 ++- Sample/IdentityServer/Config.cs | 6 ++--- .../IdentityServer/Configuration/Clients.cs | 2 +- .../Configuration/ClientsConsole.cs | 4 +-- .../Configuration/ClientsWeb.cs | 4 +-- .../IdentityServer/Configuration/Resources.cs | 8 +++--- .../Extensions/ExtensionGrantValidator.cs | 4 +-- .../Extensions/HostProfileService.cs | 6 ++--- .../NoSubjectExtensionGrantValidator.cs | 4 +-- .../Extensions/ParameterizedScopeParser.cs | 4 +-- ...ParameterizedScopeTokenRequestValidator.cs | 2 +- Sample/IdentityServer/LocalApiController.cs | 6 ++--- .../Quickstart/Account/AccountController.cs | 26 +++++++++---------- .../Quickstart/Account/ExternalController.cs | 22 ++++++++-------- .../Quickstart/Consent/ConsentController.cs | 25 +++++++++--------- .../Consent/ProcessConsentResult.cs | 2 +- .../Quickstart/Device/DeviceController.cs | 19 +++++++------- .../Diagnostics/DiagnosticsViewModel.cs | 6 ++--- .../IdentityServer/Quickstart/Extensions.cs | 2 +- .../Quickstart/Grants/GrantsController.cs | 10 +++---- .../Quickstart/Home/ErrorViewModel.cs | 2 +- .../Quickstart/Home/HomeController.cs | 4 +-- .../QuickstartIdentityServer412.csproj | 18 ++++++------- Sample/IdentityServer/Startup.cs | 14 +++++----- Sample/IdentityServer/Views/Home/Index.cshtml | 3 +-- .../IdentityServer/Views/Shared/_Nav.cshtml | 4 +-- 38 files changed, 154 insertions(+), 162 deletions(-) diff --git a/Api.Client/Api.Client.csproj b/Api.Client/Api.Client.csproj index 56b787a..436ebf1 100644 --- a/Api.Client/Api.Client.csproj +++ b/Api.Client/Api.Client.csproj @@ -2,7 +2,8 @@ Exe - net5.0 + net6.0 + latest diff --git a/IdentityServer.LdapExtension.Unit/IdentityServer.LdapExtension.Unit.csproj b/IdentityServer.LdapExtension.Unit/IdentityServer.LdapExtension.Unit.csproj index b02eb9f..f51a17c 100644 --- a/IdentityServer.LdapExtension.Unit/IdentityServer.LdapExtension.Unit.csproj +++ b/IdentityServer.LdapExtension.Unit/IdentityServer.LdapExtension.Unit.csproj @@ -1,16 +1,16 @@  - net5.0 - + net6.0 false + latest - - - - + + + + all runtime; build; native; contentfiles; analyzers diff --git a/IdentityServer.LdapExtension/Extensions/LdapUserProfileService.cs b/IdentityServer.LdapExtension/Extensions/LdapUserProfileService.cs index 8d65f32..4bad43a 100644 --- a/IdentityServer.LdapExtension/Extensions/LdapUserProfileService.cs +++ b/IdentityServer.LdapExtension/Extensions/LdapUserProfileService.cs @@ -1,8 +1,8 @@ using System.Linq; using System.Threading.Tasks; -using IdentityServer4.Extensions; -using IdentityServer4.Models; -using IdentityServer4.Services; +using Duende.IdentityServer.Extensions; +using Duende.IdentityServer.Models; +using Duende.IdentityServer.Services; using IdentityServer.LdapExtension.UserModel; using IdentityServer.LdapExtension.UserStore; using Microsoft.Extensions.Logging; diff --git a/IdentityServer.LdapExtension/Extensions/LdapUserResourceOwnerPasswordValidator.cs b/IdentityServer.LdapExtension/Extensions/LdapUserResourceOwnerPasswordValidator.cs index 75ed60c..5686fb2 100644 --- a/IdentityServer.LdapExtension/Extensions/LdapUserResourceOwnerPasswordValidator.cs +++ b/IdentityServer.LdapExtension/Extensions/LdapUserResourceOwnerPasswordValidator.cs @@ -1,7 +1,7 @@ using System; using System.Threading.Tasks; +using Duende.IdentityServer.Validation; using IdentityModel; -using IdentityServer4.Validation; using IdentityServer.LdapExtension.UserModel; using IdentityServer.LdapExtension.UserStore; using Microsoft.AspNetCore.Authentication; diff --git a/IdentityServer.LdapExtension/IdentityServer.LdapExtension.csproj b/IdentityServer.LdapExtension/IdentityServer.LdapExtension.csproj index 579cb76..c123b44 100644 --- a/IdentityServer.LdapExtension/IdentityServer.LdapExtension.csproj +++ b/IdentityServer.LdapExtension/IdentityServer.LdapExtension.csproj @@ -1,11 +1,11 @@  - net5.0;netcoreapp3.1 + net6.0 Nordès Ménard-Lamarre 2.1.14 HoNoSoFt - Extension for IdentityServer 4 in order to use LDAP as a plugin. It is also extensible enough in order to use custom LDAP schema such as OpenLdap or Active Directory. + Extension for Duende IdentityServer in order to use LDAP as a plugin. It is also extensible enough in order to use custom LDAP schema such as OpenLdap or Active Directory. 1.0.0.0 1.0.0.0 true @@ -16,17 +16,19 @@ https://www.honosoft.com/img/logo.png Nordès Ménard-Lamarre https://github.com/Nordes/IdentityServer4.LdapExtension/ - IdentityServer4, Ldap, OpenLdap, ActiveDirectory + IdentityServer4, Ldap, OpenLdap, ActiveDirectory, Duende, IdentityServer true true + latest + 4.1.0 - - + + - + diff --git a/IdentityServer.LdapExtension/LdapUserProfileService.cs b/IdentityServer.LdapExtension/LdapUserProfileService.cs index 721226b..bfcf6ff 100644 --- a/IdentityServer.LdapExtension/LdapUserProfileService.cs +++ b/IdentityServer.LdapExtension/LdapUserProfileService.cs @@ -1,10 +1,10 @@ -using IdentityServer.LdapExtension.UserStore; -using IdentityServer4.Extensions; -using IdentityServer4.Models; -using IdentityServer4.Services; -using Microsoft.Extensions.Logging; -using System.Linq; +using System.Linq; using System.Threading.Tasks; +using IdentityServer.LdapExtension.UserStore; +using Microsoft.Extensions.Logging; +using Duende.IdentityServer.Extensions; +using Duende.IdentityServer.Models; +using Duende.IdentityServer.Services; namespace IdentityServer.LdapExtension { diff --git a/IdentityServer.LdapExtension/Sinks/CustomUserLogoutSuccessEventSink.cs b/IdentityServer.LdapExtension/Sinks/CustomUserLogoutSuccessEventSink.cs index 54007d9..7603ec7 100644 --- a/IdentityServer.LdapExtension/Sinks/CustomUserLogoutSuccessEventSink.cs +++ b/IdentityServer.LdapExtension/Sinks/CustomUserLogoutSuccessEventSink.cs @@ -1,9 +1,8 @@ -using System.Threading.Tasks; -using IdentityServer4.Events; -using IdentityServer4.Services; +using System; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using System; +using Duende.IdentityServer.Events; +using Duende.IdentityServer.Services; namespace IdentityServer.LdapExtension { @@ -24,15 +23,7 @@ public Task PersistAsync(Event evt) { if (evt == null) throw new ArgumentNullException(nameof(evt)); - var json = JsonConvert.SerializeObject(evt); - _log.LogInformation(json); - - return Task.CompletedTask; - // Not working at the moment. In the doc it says to register the DI, but it still not work. - _log.LogDebug(evt.EventType.ToString()); - _log.LogDebug(evt.Id.ToString()); - _log.LogDebug(evt.Name); - _log.LogDebug(evt.Message); + _log.LogInformation("Event details: {@Event}", evt); return Task.CompletedTask; } diff --git a/IdentityServer.LdapExtension/UserStore/InMemoryUserStore.cs b/IdentityServer.LdapExtension/UserStore/InMemoryUserStore.cs index 2c93a2f..903b996 100644 --- a/IdentityServer.LdapExtension/UserStore/InMemoryUserStore.cs +++ b/IdentityServer.LdapExtension/UserStore/InMemoryUserStore.cs @@ -1,8 +1,7 @@ -using IdentityModel; +using System.Collections.Generic; +using IdentityModel; using IdentityServer.LdapExtension.Exceptions; using IdentityServer.LdapExtension.UserModel; -using System; -using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; diff --git a/IdentityServer.LdapExtension/UserStore/RedisUserStore.cs b/IdentityServer.LdapExtension/UserStore/RedisUserStore.cs index cf2158c..cf23098 100644 --- a/IdentityServer.LdapExtension/UserStore/RedisUserStore.cs +++ b/IdentityServer.LdapExtension/UserStore/RedisUserStore.cs @@ -1,14 +1,13 @@ -using IdentityModel; +using System; +using System.Collections.Generic; +using IdentityModel; using IdentityServer.LdapExtension.UserModel; -using IdentityServer4.Stores.Serialization; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; using StackExchange.Redis; -using System; -using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; +using System.Text.Json; namespace IdentityServer.LdapExtension.UserStore { @@ -27,11 +26,6 @@ public class RedisUserStore : ILdapUserStore private readonly ILdapService _authenticationService; private readonly ILogger> _logger; private IConnectionMultiplexer _redis; - private readonly JsonSerializerSettings _jsonSerializerSettings = new JsonSerializerSettings - { - Converters = new List { new ClaimConverter() }, - Formatting = Formatting.Indented - }; private TimeSpan _dataExpireIn; @@ -143,7 +137,7 @@ public IAppUser FindBySubjectId(string subjectId) if (result.HasValue) { // IMPORTANT! This line might throw an exception if we change the format/version - IAppUser foundSubjectId = JsonConvert.DeserializeObject(result.ToString(), _jsonSerializerSettings); + IAppUser foundSubjectId = JsonSerializer.Deserialize(result.ToString()); return foundSubjectId; } @@ -178,7 +172,7 @@ public IAppUser FindByUsername(string username) if (subject.HasValue) { // IMPORTANT! This line might throw an exception if we change the format/version - IAppUser foundSubjectId = JsonConvert.DeserializeObject(subject.ToString(), _jsonSerializerSettings); + IAppUser foundSubjectId = JsonSerializer.Deserialize(subject.ToString()); return foundSubjectId; } @@ -217,7 +211,7 @@ public IAppUser FindByExternalProvider(string provider, string userId) if (subject.HasValue) { // IMPORTANT! This line might throw an exception if we change the format/version - IAppUser foundSubjectId = JsonConvert.DeserializeObject(subject.ToString(), _jsonSerializerSettings); + IAppUser foundSubjectId = JsonSerializer.Deserialize(subject.ToString()); return foundSubjectId; } @@ -306,7 +300,7 @@ private void SetRedisData(IAppUser user) const string keyByUsername = "IdentityServer/OpenId/username/{0}"; // <== contains a link to the SubjectId const string keyByProviderAndUserid = "IdentityServer/OpenId/provider/{0}/userId/{1}"; // <== contains a link to the SubjectId - var userStr = JsonConvert.SerializeObject(user, _jsonSerializerSettings); + var userStr = JsonSerializer.Serialize(user); var subjectIdStorageKey = string.Format(keyBySubjectId, user.SubjectId); // add user to Redis store diff --git a/MvcVueClient/MvcVueClient.csproj b/MvcVueClient/MvcVueClient.csproj index b21efab..e7f68bc 100644 --- a/MvcVueClient/MvcVueClient.csproj +++ b/MvcVueClient/MvcVueClient.csproj @@ -1,7 +1,8 @@ - net5.0 + net6.0 true + latest diff --git a/README.md b/README.md index cb3e3f6..ac4fbad 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ IdentityServer4 Ldap Extension ([OpenLdap](https://www.openldap.org/) or [ActiveDirectory](https://en.wikipedia.org/wiki/Active_Directory)). ## Installation -The plugin is easy to install to your solution. Built using **.Net Core 3.1** and **.Net 5.0**. The Nuget package can be installed by either searching the package `IdentityServer.LdapExtension` or by typing the following command in your package console: +The plugin is easy to install to your solution. Built using **.Net 6.0**. The Nuget package can be installed by either searching the package `IdentityServer.LdapExtension` or by typing the following command in your package console: ```csharp Install-Package IdentityServer.LdapExtension @@ -29,6 +29,7 @@ Install-Package IdentityServer.LdapExtension > - Ldap Extension 2.1.7 goes with IdentityServer 2.3.x > - Ldap Extension 2.1.8 goes with IdentityServer 2.4.x > - Ldap Extension 3.1.0 goes with IdentityServer 4.1.2 +> - Ldap Extension 4.1.0 goes with Duende IdentityServer 6.1.7 ## Configuration for IdentityServer4 Server An easy extension method have been created in order to add the LDAP as a provider to your IdentityServer. For this you simply have to use the `AddLdapUsers(LdapConfigSection, StoreTypeOrCustomStore)`. The configuration has to be provided or it won't work. The configuration is described [here](#appsettings-configuration). diff --git a/Sample/Api/Api.csproj b/Sample/Api/Api.csproj index d99959e..2e66d1c 100644 --- a/Sample/Api/Api.csproj +++ b/Sample/Api/Api.csproj @@ -1,11 +1,12 @@  - net5.0 + net6.0 + latest - + diff --git a/Sample/Client/MvcClient.csproj b/Sample/Client/MvcClient.csproj index e172a80..50aa7be 100644 --- a/Sample/Client/MvcClient.csproj +++ b/Sample/Client/MvcClient.csproj @@ -1,8 +1,9 @@  - net5.0 + net6.0 true + latest diff --git a/Sample/IdentityServer/Config.cs b/Sample/IdentityServer/Config.cs index cbdcadd..b227156 100644 --- a/Sample/IdentityServer/Config.cs +++ b/Sample/IdentityServer/Config.cs @@ -1,6 +1,6 @@ -using IdentityServer4; -using IdentityServer4.Models; -using System.Collections.Generic; +using System.Collections.Generic; +using Duende.IdentityServer; +using Duende.IdentityServer.Models; namespace QuickstartIdentityServer412 { diff --git a/Sample/IdentityServer/Configuration/Clients.cs b/Sample/IdentityServer/Configuration/Clients.cs index 44eb0b4..053e40f 100644 --- a/Sample/IdentityServer/Configuration/Clients.cs +++ b/Sample/IdentityServer/Configuration/Clients.cs @@ -2,8 +2,8 @@ // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -using IdentityServer4.Models; using System.Collections.Generic; +using Duende.IdentityServer.Models; namespace IdentityServerHost.Configuration { diff --git a/Sample/IdentityServer/Configuration/ClientsConsole.cs b/Sample/IdentityServer/Configuration/ClientsConsole.cs index 0b3dc18..d239108 100644 --- a/Sample/IdentityServer/Configuration/ClientsConsole.cs +++ b/Sample/IdentityServer/Configuration/ClientsConsole.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; -using IdentityServer4; -using IdentityServer4.Models; +using Duende.IdentityServer; +using Duende.IdentityServer.Models; namespace IdentityServerHost.Configuration { diff --git a/Sample/IdentityServer/Configuration/ClientsWeb.cs b/Sample/IdentityServer/Configuration/ClientsWeb.cs index 444b887..75c299d 100644 --- a/Sample/IdentityServer/Configuration/ClientsWeb.cs +++ b/Sample/IdentityServer/Configuration/ClientsWeb.cs @@ -2,8 +2,8 @@ // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.using System.Collections.Generic; using System.Collections.Generic; -using IdentityServer4; -using IdentityServer4.Models; +using Duende.IdentityServer; +using Duende.IdentityServer.Models; namespace IdentityServerHost.Configuration { diff --git a/Sample/IdentityServer/Configuration/Resources.cs b/Sample/IdentityServer/Configuration/Resources.cs index 4e0e2df..3f4ad14 100644 --- a/Sample/IdentityServer/Configuration/Resources.cs +++ b/Sample/IdentityServer/Configuration/Resources.cs @@ -2,10 +2,10 @@ // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -using IdentityModel; -using IdentityServer4.Models; using System.Collections.Generic; -using static IdentityServer4.IdentityServerConstants; +using IdentityModel; +using Duende.IdentityServer; +using Duende.IdentityServer.Models; namespace IdentityServerHost.Configuration { @@ -29,7 +29,7 @@ public class Resources new[] { // local API scope - new ApiScope(LocalApi.ScopeName), + new ApiScope(IdentityServerConstants.LocalApi.ScopeName), // resource specific scopes new ApiScope("resource1.scope1"), diff --git a/Sample/IdentityServer/Extensions/ExtensionGrantValidator.cs b/Sample/IdentityServer/Extensions/ExtensionGrantValidator.cs index 04d19a8..f9fcbd8 100644 --- a/Sample/IdentityServer/Extensions/ExtensionGrantValidator.cs +++ b/Sample/IdentityServer/Extensions/ExtensionGrantValidator.cs @@ -2,9 +2,9 @@ // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -using IdentityServer4.Models; -using IdentityServer4.Validation; using System.Threading.Tasks; +using Duende.IdentityServer.Models; +using Duende.IdentityServer.Validation; namespace IdentityServerHost.Extensions { diff --git a/Sample/IdentityServer/Extensions/HostProfileService.cs b/Sample/IdentityServer/Extensions/HostProfileService.cs index 1ffde33..4d4b5b6 100644 --- a/Sample/IdentityServer/Extensions/HostProfileService.cs +++ b/Sample/IdentityServer/Extensions/HostProfileService.cs @@ -1,9 +1,9 @@ using System.Linq; using System.Security.Claims; using System.Threading.Tasks; -using IdentityServer.LdapExtension; -using IdentityServer.LdapExtension.UserStore; -using IdentityServer4.Models; +using Duende.IdentityServer.Models; +using IdentityServer.LdapExtension; +using IdentityServer.LdapExtension.UserStore; using Microsoft.Extensions.Logging; namespace IdentityServerHost.Extensions diff --git a/Sample/IdentityServer/Extensions/NoSubjectExtensionGrantValidator.cs b/Sample/IdentityServer/Extensions/NoSubjectExtensionGrantValidator.cs index a33cd4f..f50e329 100644 --- a/Sample/IdentityServer/Extensions/NoSubjectExtensionGrantValidator.cs +++ b/Sample/IdentityServer/Extensions/NoSubjectExtensionGrantValidator.cs @@ -2,9 +2,9 @@ // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -using IdentityServer4.Models; -using IdentityServer4.Validation; using System.Threading.Tasks; +using Duende.IdentityServer.Models; +using Duende.IdentityServer.Validation; namespace IdentityServerHost.Extensions { diff --git a/Sample/IdentityServer/Extensions/ParameterizedScopeParser.cs b/Sample/IdentityServer/Extensions/ParameterizedScopeParser.cs index 2c81e39..99fe0f5 100644 --- a/Sample/IdentityServer/Extensions/ParameterizedScopeParser.cs +++ b/Sample/IdentityServer/Extensions/ParameterizedScopeParser.cs @@ -1,6 +1,6 @@ -using IdentityServer4.Validation; +using System; +using Duende.IdentityServer.Validation; using Microsoft.Extensions.Logging; -using System; namespace IdentityServerHost.Extensions { diff --git a/Sample/IdentityServer/Extensions/ParameterizedScopeTokenRequestValidator.cs b/Sample/IdentityServer/Extensions/ParameterizedScopeTokenRequestValidator.cs index e4a336f..3b6876f 100644 --- a/Sample/IdentityServer/Extensions/ParameterizedScopeTokenRequestValidator.cs +++ b/Sample/IdentityServer/Extensions/ParameterizedScopeTokenRequestValidator.cs @@ -1,7 +1,7 @@ using System.Linq; using System.Security.Claims; using System.Threading.Tasks; -using IdentityServer4.Validation; +using Duende.IdentityServer.Validation; namespace IdentityServerHost.Extensions { diff --git a/Sample/IdentityServer/LocalApiController.cs b/Sample/IdentityServer/LocalApiController.cs index c4cef13..2f24fad 100644 --- a/Sample/IdentityServer/LocalApiController.cs +++ b/Sample/IdentityServer/LocalApiController.cs @@ -2,15 +2,15 @@ // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. +using System.Linq; +using Duende.IdentityServer; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using System.Linq; -using static IdentityServer4.IdentityServerConstants; namespace IdentityServerHost { [Route("localApi")] - [Authorize(LocalApi.PolicyName)] + [Authorize(IdentityServerConstants.LocalApi.PolicyName)] public class LocalApiController : ControllerBase { public IActionResult Get() diff --git a/Sample/IdentityServer/Quickstart/Account/AccountController.cs b/Sample/IdentityServer/Quickstart/Account/AccountController.cs index eb7904e..f5d4c27 100644 --- a/Sample/IdentityServer/Quickstart/Account/AccountController.cs +++ b/Sample/IdentityServer/Quickstart/Account/AccountController.cs @@ -2,22 +2,22 @@ // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. +using System; +using System.Linq; +using System.Threading.Tasks; +using Duende.IdentityServer; +using Duende.IdentityServer.Events; +using Duende.IdentityServer.Extensions; +using Duende.IdentityServer.Models; +using Duende.IdentityServer.Services; +using Duende.IdentityServer.Stores; using IdentityModel; -using IdentityServer.LdapExtension.UserModel; -using IdentityServer.LdapExtension.UserStore; -using IdentityServer4; -using IdentityServer4.Events; -using IdentityServer4.Extensions; -using IdentityServer4.Models; -using IdentityServer4.Services; -using IdentityServer4.Stores; +using IdentityServer.LdapExtension.UserModel; +using IdentityServer.LdapExtension.UserStore; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using System; -using System.Linq; -using System.Threading.Tasks; namespace IdentityServerHost.Quickstart.UI { @@ -242,7 +242,7 @@ private async Task BuildLoginViewModelAsync(string returnUrl) var context = await _interaction.GetAuthorizationContextAsync(returnUrl); if (context?.IdP != null && await _schemeProvider.GetSchemeAsync(context.IdP) != null) { - var local = context.IdP == IdentityServer4.IdentityServerConstants.LocalIdentityProvider; + var local = context.IdP == IdentityServerConstants.LocalIdentityProvider; // this is meant to short circuit the UI and only trigger the one external IdP var vm = new LoginViewModel @@ -344,7 +344,7 @@ private async Task BuildLoggedOutViewModelAsync(string logou if (User?.Identity.IsAuthenticated == true) { var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value; - if (idp != null && idp != IdentityServer4.IdentityServerConstants.LocalIdentityProvider) + if (idp != null && idp != IdentityServerConstants.LocalIdentityProvider) { var providerSupportsSignout = await HttpContext.GetSchemeSupportsSignOutAsync(idp); if (providerSupportsSignout) diff --git a/Sample/IdentityServer/Quickstart/Account/ExternalController.cs b/Sample/IdentityServer/Quickstart/Account/ExternalController.cs index 931c40b..9f4309a 100644 --- a/Sample/IdentityServer/Quickstart/Account/ExternalController.cs +++ b/Sample/IdentityServer/Quickstart/Account/ExternalController.cs @@ -1,20 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; using IdentityModel; -using IdentityServer.LdapExtension.UserModel; -using IdentityServer.LdapExtension.UserStore; -using IdentityServer4; -using IdentityServer4.Events; -using IdentityServer4.Services; -using IdentityServer4.Stores; +using IdentityServer.LdapExtension.UserModel; +using IdentityServer.LdapExtension.UserStore; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; using System.Security.Claims; using System.Threading.Tasks; +using Duende.IdentityServer; +using Duende.IdentityServer.Events; +using Duende.IdentityServer.Services; +using Duende.IdentityServer.Stores; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; namespace IdentityServerHost.Quickstart.UI { diff --git a/Sample/IdentityServer/Quickstart/Consent/ConsentController.cs b/Sample/IdentityServer/Quickstart/Consent/ConsentController.cs index 6c3aa44..96bdbd9 100644 --- a/Sample/IdentityServer/Quickstart/Consent/ConsentController.cs +++ b/Sample/IdentityServer/Quickstart/Consent/ConsentController.cs @@ -2,18 +2,19 @@ // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -using IdentityServer4.Events; -using IdentityServer4.Models; -using IdentityServer4.Services; -using IdentityServer4.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Duende.IdentityServer; +using Duende.IdentityServer.Events; +using Duende.IdentityServer.Extensions; +using Duende.IdentityServer.Models; +using Duende.IdentityServer.Services; +using Duende.IdentityServer.Validation; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; -using System.Linq; -using System.Threading.Tasks; -using IdentityServer4.Validation; -using System.Collections.Generic; -using System; namespace IdentityServerHost.Quickstart.UI { @@ -120,7 +121,7 @@ private async Task ProcessConsent(ConsentInputModel model) var scopes = model.ScopesConsented; if (ConsentOptions.EnableOfflineAccess == false) { - scopes = scopes.Where(x => x != IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess); + scopes = scopes.Where(x => x != IdentityServerConstants.StandardScopes.OfflineAccess); } grantedConsent = new ConsentResponse @@ -208,7 +209,7 @@ private ConsentViewModel CreateConsentViewModel( } if (ConsentOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess) { - apiScopes.Add(GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess) || model == null)); + apiScopes.Add(GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServerConstants.StandardScopes.OfflineAccess) || model == null)); } vm.ApiScopes = apiScopes; @@ -251,7 +252,7 @@ private ScopeViewModel GetOfflineAccessScope(bool check) { return new ScopeViewModel { - Value = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess, + Value = IdentityServerConstants.StandardScopes.OfflineAccess, DisplayName = ConsentOptions.OfflineAccessDisplayName, Description = ConsentOptions.OfflineAccessDescription, Emphasize = true, diff --git a/Sample/IdentityServer/Quickstart/Consent/ProcessConsentResult.cs b/Sample/IdentityServer/Quickstart/Consent/ProcessConsentResult.cs index 1d331df..e18d8f5 100644 --- a/Sample/IdentityServer/Quickstart/Consent/ProcessConsentResult.cs +++ b/Sample/IdentityServer/Quickstart/Consent/ProcessConsentResult.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -using IdentityServer4.Models; +using Duende.IdentityServer.Models; namespace IdentityServerHost.Quickstart.UI { diff --git a/Sample/IdentityServer/Quickstart/Device/DeviceController.cs b/Sample/IdentityServer/Quickstart/Device/DeviceController.cs index d7d07ae..c9726fb 100644 --- a/Sample/IdentityServer/Quickstart/Device/DeviceController.cs +++ b/Sample/IdentityServer/Quickstart/Device/DeviceController.cs @@ -6,12 +6,13 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using IdentityServer4.Configuration; -using IdentityServer4.Events; -using IdentityServer4.Extensions; -using IdentityServer4.Models; -using IdentityServer4.Services; -using IdentityServer4.Validation; +using Duende.IdentityServer; +using Duende.IdentityServer.Configuration; +using Duende.IdentityServer.Events; +using Duende.IdentityServer.Extensions; +using Duende.IdentityServer.Models; +using Duende.IdentityServer.Services; +using Duende.IdentityServer.Validation; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -102,7 +103,7 @@ private async Task ProcessConsent(DeviceAuthorizationInput var scopes = model.ScopesConsented; if (ConsentOptions.EnableOfflineAccess == false) { - scopes = scopes.Where(x => x != IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess); + scopes = scopes.Where(x => x != IdentityServerConstants.StandardScopes.OfflineAccess); } grantedConsent = new ConsentResponse @@ -184,7 +185,7 @@ private DeviceAuthorizationViewModel CreateConsentViewModel(string userCode, Dev } if (ConsentOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess) { - apiScopes.Add(GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess) || model == null)); + apiScopes.Add(GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServerConstants.StandardScopes.OfflineAccess) || model == null)); } vm.ApiScopes = apiScopes; @@ -221,7 +222,7 @@ private ScopeViewModel GetOfflineAccessScope(bool check) { return new ScopeViewModel { - Value = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess, + Value = IdentityServerConstants.StandardScopes.OfflineAccess, DisplayName = ConsentOptions.OfflineAccessDisplayName, Description = ConsentOptions.OfflineAccessDescription, Emphasize = true, diff --git a/Sample/IdentityServer/Quickstart/Diagnostics/DiagnosticsViewModel.cs b/Sample/IdentityServer/Quickstart/Diagnostics/DiagnosticsViewModel.cs index f43c768..0e51863 100644 --- a/Sample/IdentityServer/Quickstart/Diagnostics/DiagnosticsViewModel.cs +++ b/Sample/IdentityServer/Quickstart/Diagnostics/DiagnosticsViewModel.cs @@ -2,11 +2,11 @@ // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. +using System.Collections.Generic; using IdentityModel; using Microsoft.AspNetCore.Authentication; -using Newtonsoft.Json; -using System.Collections.Generic; using System.Text; +using System.Text.Json; namespace IdentityServerHost.Quickstart.UI { @@ -22,7 +22,7 @@ public DiagnosticsViewModel(AuthenticateResult result) var bytes = Base64Url.Decode(encoded); var value = Encoding.UTF8.GetString(bytes); - Clients = JsonConvert.DeserializeObject(value); + Clients = JsonSerializer.Deserialize(value); } } diff --git a/Sample/IdentityServer/Quickstart/Extensions.cs b/Sample/IdentityServer/Quickstart/Extensions.cs index 6c720b7..d441f52 100644 --- a/Sample/IdentityServer/Quickstart/Extensions.cs +++ b/Sample/IdentityServer/Quickstart/Extensions.cs @@ -1,5 +1,5 @@ using System; -using IdentityServer4.Models; +using Duende.IdentityServer.Models; using Microsoft.AspNetCore.Mvc; namespace IdentityServerHost.Quickstart.UI diff --git a/Sample/IdentityServer/Quickstart/Grants/GrantsController.cs b/Sample/IdentityServer/Quickstart/Grants/GrantsController.cs index 128ce59..0a26714 100644 --- a/Sample/IdentityServer/Quickstart/Grants/GrantsController.cs +++ b/Sample/IdentityServer/Quickstart/Grants/GrantsController.cs @@ -2,15 +2,15 @@ // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -using IdentityServer4.Services; -using IdentityServer4.Stores; -using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Duende.IdentityServer.Events; +using Duende.IdentityServer.Extensions; +using Duende.IdentityServer.Services; +using Duende.IdentityServer.Stores; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; -using IdentityServer4.Events; -using IdentityServer4.Extensions; namespace IdentityServerHost.Quickstart.UI { diff --git a/Sample/IdentityServer/Quickstart/Home/ErrorViewModel.cs b/Sample/IdentityServer/Quickstart/Home/ErrorViewModel.cs index a29a21a..ed2896d 100644 --- a/Sample/IdentityServer/Quickstart/Home/ErrorViewModel.cs +++ b/Sample/IdentityServer/Quickstart/Home/ErrorViewModel.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -using IdentityServer4.Models; +using Duende.IdentityServer.Models; namespace IdentityServerHost.Quickstart.UI { diff --git a/Sample/IdentityServer/Quickstart/Home/HomeController.cs b/Sample/IdentityServer/Quickstart/Home/HomeController.cs index 9cf0678..0aa3387 100644 --- a/Sample/IdentityServer/Quickstart/Home/HomeController.cs +++ b/Sample/IdentityServer/Quickstart/Home/HomeController.cs @@ -2,13 +2,13 @@ // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -using IdentityServer4.Services; +using System.Threading.Tasks; +using Duende.IdentityServer.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using System.Threading.Tasks; namespace IdentityServerHost.Quickstart.UI { diff --git a/Sample/IdentityServer/QuickstartIdentityServer412.csproj b/Sample/IdentityServer/QuickstartIdentityServer412.csproj index 7295825..6638b35 100644 --- a/Sample/IdentityServer/QuickstartIdentityServer412.csproj +++ b/Sample/IdentityServer/QuickstartIdentityServer412.csproj @@ -1,8 +1,9 @@ - net5.0 + net6.0 InProcess + latest @@ -14,19 +15,18 @@ - + + - + - + - + - - - - + + \ No newline at end of file diff --git a/Sample/IdentityServer/Startup.cs b/Sample/IdentityServer/Startup.cs index e7b1863..aa0fafa 100644 --- a/Sample/IdentityServer/Startup.cs +++ b/Sample/IdentityServer/Startup.cs @@ -5,7 +5,6 @@ using System; using IdentityServerHost.Configuration; using IdentityModel; -using IdentityServer4; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -17,13 +16,14 @@ using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; +using Duende.IdentityServer; using IdentityServerHost.Extensions; using Microsoft.AspNetCore.Authentication.Certificate; using Microsoft.AspNetCore.HttpOverrides; -using IdentityServer.LdapExtension.Extensions; -using IdentityServer.LdapExtension.UserModel; -using QuickstartIdentityServer412; - +using IdentityServer.LdapExtension.Extensions; +using IdentityServer.LdapExtension.UserModel; +using QuickstartIdentityServer412; + namespace IdentityServerHost { public class Startup @@ -59,9 +59,9 @@ public void ConfigureServices(IServiceCollection services) }) //.AddInMemoryClients(Clients.Get()) // [Original code] //.AddInMemoryIdentityResources(Resources.IdentityResources) [Original code] - //.AddInMemoryApiScopes(Resources.ApiScopes) // [Original code] + //.AddInMemoryApiScopes(Resources.ApiScopes) // [Original code] .AddInMemoryClients(Config.GetClients()) // [LDAP API Example] - .AddInMemoryIdentityResources(Config.IdentityResources()) // [LDAP API Example] + .AddInMemoryIdentityResources(Config.IdentityResources()) // [LDAP API Example] .AddInMemoryApiScopes(Config.GetApiScope()) // [LDAP API Example] .AddInMemoryApiResources(Resources.ApiResources) .AddSigningCredential() diff --git a/Sample/IdentityServer/Views/Home/Index.cshtml b/Sample/IdentityServer/Views/Home/Index.cshtml index e0fb30c..c02b230 100644 --- a/Sample/IdentityServer/Views/Home/Index.cshtml +++ b/Sample/IdentityServer/Views/Home/Index.cshtml @@ -1,8 +1,7 @@ -@using System.Diagnostics @using System.Reflection @{ - var version = typeof(IdentityServer4.Hosting.IdentityServerMiddleware).Assembly.GetCustomAttribute()?.InformationalVersion.Split('+').First(); + var version = typeof(Duende.IdentityServer.Hosting.IdentityServerMiddleware).Assembly.GetCustomAttribute()?.InformationalVersion.Split('+').First(); }
diff --git a/Sample/IdentityServer/Views/Shared/_Nav.cshtml b/Sample/IdentityServer/Views/Shared/_Nav.cshtml index 6ab6b70..6e68a5b 100644 --- a/Sample/IdentityServer/Views/Shared/_Nav.cshtml +++ b/Sample/IdentityServer/Views/Shared/_Nav.cshtml @@ -1,5 +1,5 @@ -@using IdentityServer4.Extensions - +@using Duende.IdentityServer.Extensions +@using Microsoft.AspNetCore.Mvc.TagHelpers @{ string name = null; if (!true.Equals(ViewData["signed-out"])) From b578318e990071b37b7c112782818b67c65b051d Mon Sep 17 00:00:00 2001 From: Radu Terec <65555860+RaduTerec@users.noreply.github.com> Date: Fri, 21 Oct 2022 17:16:29 +0200 Subject: [PATCH 02/11] Updated Readme.md Removed references to IdentityServer4 from text. --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ac4fbad..11d430b 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ [![Build status](https://ci.appveyor.com/api/projects/status/k26pepb32vka29w2/branch/master?svg=true)](https://ci.appveyor.com/project/Nordes/identityserver4-ldapextension/branch/master) [![NuGet](https://img.shields.io/nuget/v/IdentityServer.LdapExtension.svg)](https://www.nuget.org/packages/IdentityServer.LdapExtension/) -- [IdentityServer4.LdapExtension](#identityserver4ldapextension) +- [IdentityServer.LdapExtension](#identityserverldapextension) - [Installation](#installation) - - [Configuration for IdentityServer4 Server](#configuration-for-identityserver4-server) + - [Configuration for IdentityServer Server](#configuration-for-identityserver-server) - [AppSettings Configuration](#appsettings-configuration) - [Multiple concurent Ldap (For different DN, or totally different Ldap)](#multiple-concurent-ldap-for-different-dn-or-totally-different-ldap) - [Quick and Simple Example of a Configuration](#quick-and-simple-example-of-a-configuration) @@ -14,8 +14,8 @@ - [Special thanks to](#special-thanks-to) - [License](#license) -# IdentityServer4.LdapExtension -IdentityServer4 Ldap Extension ([OpenLdap](https://www.openldap.org/) or [ActiveDirectory](https://en.wikipedia.org/wiki/Active_Directory)). +# IdentityServer.LdapExtension +IdentityServer Ldap Extension ([OpenLdap](https://www.openldap.org/) or [ActiveDirectory](https://en.wikipedia.org/wiki/Active_Directory)). ## Installation The plugin is easy to install to your solution. Built using **.Net 6.0**. The Nuget package can be installed by either searching the package `IdentityServer.LdapExtension` or by typing the following command in your package console: @@ -31,7 +31,7 @@ Install-Package IdentityServer.LdapExtension > - Ldap Extension 3.1.0 goes with IdentityServer 4.1.2 > - Ldap Extension 4.1.0 goes with Duende IdentityServer 6.1.7 -## Configuration for IdentityServer4 Server +## Configuration for IdentityServer Server An easy extension method have been created in order to add the LDAP as a provider to your IdentityServer. For this you simply have to use the `AddLdapUsers(LdapConfigSection, StoreTypeOrCustomStore)`. The configuration has to be provided or it won't work. The configuration is described [here](#appsettings-configuration). In the `Startup.cs` under `ConfigureServices` method, you will have something similar to the following by default (Starter pack for IdentityServer). The last line is what you will need to add in order to get started. From c0fba72de8f1280e83586a74a171783294fa5d65 Mon Sep 17 00:00:00 2001 From: Nordes Date: Mon, 12 Apr 2021 21:10:50 -0400 Subject: [PATCH 03/11] Upgrade to IS 4.1.2 Signed-off-by: Radu Terec --- Api.Client/Api.Client.csproj | 12 + Api.Client/ClientCredentialsTokenRequest.cs | 10 + Api.Client/Program.cs | 63 + .../IdentityServer.LdapExtension.Unit.csproj | 8 +- IdentityServer.LdapExtension.sln | 49 +- .../DirectoryServices/ILdapConnection.cs | 1 - .../Extensions/EnumExtension.cs | 2 + .../IdentityServer.LdapExtension.csproj | 10 +- IdentityServer.LdapExtension/LdapService.cs | 49 +- .../LdapUserProfileService.cs | 69 + MvcVueClient/MvcVueClient.csproj | 8 +- Sample/Api/Api.csproj | 2 +- Sample/Api/Program.cs | 16 +- Sample/Api/Startup.cs | 46 +- Sample/Client/MvcClient.csproj | 6 +- Sample/Client/Startup.cs | 96 +- Sample/IdentityServer/AppSettings.json | 163 +- Sample/IdentityServer/Config.cs | 105 + .../IdentityServer/Configuration/Clients.cs | 22 + .../Configuration/ClientsConsole.cs | 221 + .../Configuration/ClientsWeb.cs | 132 + .../IdentityServer/Configuration/Resources.cs | 80 + .../Extensions/ExtensionGrantValidator.cs | 35 + .../Extensions/HostProfileService.cs | 28 + .../NoSubjectExtensionGrantValidator.cs | 35 + .../Extensions/ParameterizedScopeParser.cs | 47 + ...ParameterizedScopeTokenRequestValidator.cs | 21 + .../Extensions/SameSiteHandlingExtensions.cs | 72 + .../Keys/identityserver.test.ecdsa.p12 | Bin 0 -> 2800 bytes .../Keys/identityserver.test.rsa.p12 | Bin 0 -> 4079 bytes Sample/IdentityServer/LocalApiController.cs | 22 + Sample/IdentityServer/Program.cs | 87 +- .../Quickstart/Account/AccountController.cs | 712 +- .../Quickstart/Account/AccountOptions.cs | 9 +- .../Quickstart/Account/ExternalController.cs | 195 + .../Quickstart/Account/ExternalProvider.cs | 2 +- .../Quickstart/Account/LoggedOutViewModel.cs | 4 +- .../Quickstart/Account/LoginInputModel.cs | 5 +- .../Quickstart/Account/LoginViewModel.cs | 10 +- .../Quickstart/Account/LogoutInputModel.cs | 2 +- .../Quickstart/Account/LogoutViewModel.cs | 6 +- .../Quickstart/Account/RedirectViewModel.cs | 12 + .../Quickstart/Consent/ConsentController.cs | 206 +- .../Quickstart/Consent/ConsentInputModel.cs | 3 +- .../Quickstart/Consent/ConsentOptions.cs | 2 +- .../Quickstart/Consent/ConsentViewModel.cs | 4 +- .../Consent/ProcessConsentResult.cs | 7 +- .../Quickstart/Consent/ScopeViewModel.cs | 4 +- .../Device/DeviceAuthorizationInputModel.cs | 11 + .../Device/DeviceAuthorizationViewModel.cs | 12 + .../Quickstart/Device/DeviceController.cs | 232 + .../Diagnostics/DiagnosticsController.cs | 15 +- .../Diagnostics/DiagnosticsViewModel.cs | 32 + .../IdentityServer/Quickstart/Extensions.cs | 27 + .../Quickstart/Grants/GrantsController.cs | 18 +- .../Quickstart/Grants/GrantsViewModel.cs | 3 +- .../Quickstart/Home/ErrorViewModel.cs | 11 +- .../Quickstart/Home/HomeController.cs | 30 +- .../Quickstart/SecurityHeadersAttribute.cs | 2 +- .../QuickstartIdentityServer412.csproj | 32 + Sample/IdentityServer/Startup.cs | 318 +- .../Views/Account/AccessDenied.cshtml | 7 + .../Views/Account/LoggedOut.cshtml | 2 +- .../IdentityServer/Views/Account/Login.cshtml | 81 +- .../Views/Account/Logout.cshtml | 22 +- .../IdentityServer/Views/Consent/Index.cshtml | 136 +- .../Views/Device/Success.cshtml | 7 + .../Views/Device/UserCodeCapture.cshtml | 23 + .../Views/Device/UserCodeConfirmation.cshtml | 108 + .../Views/Diagnostics/Index.cshtml | 81 +- .../IdentityServer/Views/Grants/Index.cshtml | 78 +- Sample/IdentityServer/Views/Home/Index.cshtml | 64 +- .../IdentityServer/Views/Shared/Error.cshtml | 7 +- .../Views/Shared/Redirect.cshtml | 11 + .../Views/Shared/_Layout.cshtml | 59 +- .../IdentityServer/Views/Shared/_Nav.cshtml | 33 + .../Views/Shared/_ScopeListItem.cshtml | 34 + .../IdentityServer/Views/_ViewImports.cshtml | 2 +- Sample/IdentityServer/compilerconfig.json | 6 + .../compilerconfig.json.defaults | 63 + Sample/IdentityServer/wwwroot/css/site.css | 100 +- .../IdentityServer/wwwroot/css/site.min.css | 2 +- Sample/IdentityServer/wwwroot/css/site.scss | 42 + .../wwwroot/js/signin-redirect.js | 1 + .../wwwroot/lib/bootstrap/LICENSE | 22 + .../wwwroot/lib/bootstrap/README.md | 209 + .../lib/bootstrap/dist/css/bootstrap-grid.css | 3899 ++++++ .../bootstrap/dist/css/bootstrap-grid.css.map | 1 + .../bootstrap/dist/css/bootstrap-grid.min.css | 7 + .../dist/css/bootstrap-grid.min.css.map | 1 + .../bootstrap/dist/css/bootstrap-reboot.css | 327 + .../dist/css/bootstrap-reboot.css.map | 1 + .../dist/css/bootstrap-reboot.min.css | 8 + .../dist/css/bootstrap-reboot.min.css.map | 1 + .../lib/bootstrap/dist/css/bootstrap.css | 10224 +++++++++++++++ .../lib/bootstrap/dist/css/bootstrap.css.map | 1 + .../lib/bootstrap/dist/css/bootstrap.min.css | 7 + .../bootstrap/dist/css/bootstrap.min.css.map | 1 + .../lib/bootstrap/dist/js/bootstrap.bundle.js | 7134 ++++++++++ .../bootstrap/dist/js/bootstrap.bundle.js.map | 1 + .../bootstrap/dist/js/bootstrap.bundle.min.js | 7 + .../dist/js/bootstrap.bundle.min.js.map | 1 + .../lib/bootstrap/dist/js/bootstrap.js | 4521 +++++++ .../lib/bootstrap/dist/js/bootstrap.js.map | 1 + .../lib/bootstrap/dist/js/bootstrap.min.js | 7 + .../bootstrap/dist/js/bootstrap.min.js.map | 1 + .../wwwroot/lib/bootstrap/scss/_alert.scss | 51 + .../wwwroot/lib/bootstrap/scss/_badge.scss | 54 + .../lib/bootstrap/scss/_breadcrumb.scss | 42 + .../lib/bootstrap/scss/_button-group.scss | 163 + .../wwwroot/lib/bootstrap/scss/_buttons.scss | 139 + .../wwwroot/lib/bootstrap/scss/_card.scss | 278 + .../wwwroot/lib/bootstrap/scss/_carousel.scss | 197 + .../wwwroot/lib/bootstrap/scss/_close.scss | 41 + .../wwwroot/lib/bootstrap/scss/_code.scss | 48 + .../lib/bootstrap/scss/_custom-forms.scss | 521 + .../wwwroot/lib/bootstrap/scss/_dropdown.scss | 191 + .../wwwroot/lib/bootstrap/scss/_forms.scss | 338 + .../lib/bootstrap/scss/_functions.scss | 134 + .../wwwroot/lib/bootstrap/scss/_grid.scss | 69 + .../wwwroot/lib/bootstrap/scss/_images.scss | 42 + .../lib/bootstrap/scss/_input-group.scss | 191 + .../lib/bootstrap/scss/_jumbotron.scss | 17 + .../lib/bootstrap/scss/_list-group.scss | 158 + .../wwwroot/lib/bootstrap/scss/_media.scss | 8 + .../wwwroot/lib/bootstrap/scss/_mixins.scss | 47 + .../wwwroot/lib/bootstrap/scss/_modal.scss | 239 + .../wwwroot/lib/bootstrap/scss/_nav.scss | 120 + .../wwwroot/lib/bootstrap/scss/_navbar.scss | 324 + .../lib/bootstrap/scss/_pagination.scss | 73 + .../wwwroot/lib/bootstrap/scss/_popover.scss | 170 + .../wwwroot/lib/bootstrap/scss/_print.scss | 141 + .../wwwroot/lib/bootstrap/scss/_progress.scss | 46 + .../wwwroot/lib/bootstrap/scss/_reboot.scss | 482 + .../wwwroot/lib/bootstrap/scss/_root.scss | 20 + .../wwwroot/lib/bootstrap/scss/_spinners.scss | 55 + .../wwwroot/lib/bootstrap/scss/_tables.scss | 185 + .../wwwroot/lib/bootstrap/scss/_toasts.scss | 44 + .../wwwroot/lib/bootstrap/scss/_tooltip.scss | 115 + .../lib/bootstrap/scss/_transitions.scss | 20 + .../wwwroot/lib/bootstrap/scss/_type.scss | 125 + .../lib/bootstrap/scss/_utilities.scss | 17 + .../lib/bootstrap/scss/_variables.scss | 1143 ++ .../lib/bootstrap/scss/bootstrap-grid.scss | 29 + .../lib/bootstrap/scss/bootstrap-reboot.scss | 12 + .../wwwroot/lib/bootstrap/scss/bootstrap.scss | 44 + .../lib/bootstrap/scss/mixins/_alert.scss | 13 + .../scss/mixins/_background-variant.scss | 22 + .../lib/bootstrap/scss/mixins/_badge.scss | 17 + .../bootstrap/scss/mixins/_border-radius.scss | 63 + .../bootstrap/scss/mixins/_box-shadow.scss | 20 + .../bootstrap/scss/mixins/_breakpoints.scss | 123 + .../lib/bootstrap/scss/mixins/_buttons.scss | 110 + .../lib/bootstrap/scss/mixins/_caret.scss | 62 + .../lib/bootstrap/scss/mixins/_clearfix.scss | 7 + .../lib/bootstrap/scss/mixins/_deprecate.scss | 10 + .../lib/bootstrap/scss/mixins/_float.scss | 14 + .../lib/bootstrap/scss/mixins/_forms.scss | 177 + .../lib/bootstrap/scss/mixins/_gradients.scss | 45 + .../scss/mixins/_grid-framework.scss | 71 + .../lib/bootstrap/scss/mixins/_grid.scss | 69 + .../lib/bootstrap/scss/mixins/_hover.scss | 37 + .../lib/bootstrap/scss/mixins/_image.scss | 36 + .../bootstrap/scss/mixins/_list-group.scss | 21 + .../lib/bootstrap/scss/mixins/_lists.scss | 7 + .../bootstrap/scss/mixins/_nav-divider.scss | 11 + .../bootstrap/scss/mixins/_pagination.scss | 22 + .../bootstrap/scss/mixins/_reset-text.scss | 17 + .../lib/bootstrap/scss/mixins/_resize.scss | 6 + .../bootstrap/scss/mixins/_screen-reader.scss | 34 + .../lib/bootstrap/scss/mixins/_size.scss | 7 + .../lib/bootstrap/scss/mixins/_table-row.scss | 39 + .../bootstrap/scss/mixins/_text-emphasis.scss | 17 + .../lib/bootstrap/scss/mixins/_text-hide.scss | 11 + .../bootstrap/scss/mixins/_text-truncate.scss | 8 + .../bootstrap/scss/mixins/_transition.scss | 16 + .../bootstrap/scss/mixins/_visibility.scss | 8 + .../lib/bootstrap/scss/utilities/_align.scss | 8 + .../bootstrap/scss/utilities/_background.scss | 19 + .../bootstrap/scss/utilities/_borders.scss | 75 + .../bootstrap/scss/utilities/_clearfix.scss | 3 + .../bootstrap/scss/utilities/_display.scss | 26 + .../lib/bootstrap/scss/utilities/_embed.scss | 39 + .../lib/bootstrap/scss/utilities/_flex.scss | 51 + .../lib/bootstrap/scss/utilities/_float.scss | 11 + .../bootstrap/scss/utilities/_overflow.scss | 5 + .../bootstrap/scss/utilities/_position.scss | 32 + .../scss/utilities/_screenreaders.scss | 11 + .../bootstrap/scss/utilities/_shadows.scss | 6 + .../lib/bootstrap/scss/utilities/_sizing.scss | 20 + .../bootstrap/scss/utilities/_spacing.scss | 73 + .../scss/utilities/_stretched-link.scss | 19 + .../lib/bootstrap/scss/utilities/_text.scss | 72 + .../bootstrap/scss/utilities/_visibility.scss | 13 + .../lib/bootstrap/scss/vendor/_rfs.scss | 204 + .../wwwroot/lib/jquery/LICENSE.txt | 20 + .../wwwroot/lib/jquery/README.md | 62 + .../wwwroot/lib/jquery/dist/jquery.js | 10872 ++++++++++++++++ .../wwwroot/lib/jquery/dist/jquery.min.js | 2 + .../wwwroot/lib/jquery/dist/jquery.min.map | 1 + .../wwwroot/lib/jquery/dist/jquery.slim.js | 8777 +++++++++++++ .../lib/jquery/dist/jquery.slim.min.js | 2 + .../lib/jquery/dist/jquery.slim.min.map | 1 + .../AppSettings.json | 60 + .../Data/InMemoryInitConfig.cs | 0 Sample/deprecated_IdentityServer/Program.cs | 24 + .../Quickstart/Account/AccountController.cs | 345 + .../Quickstart/Account/AccountOptions.cs | 27 + .../Quickstart/Account/AccountService.cs | 0 .../Quickstart/Account/ExternalProvider.cs | 12 + .../Quickstart/Account/LoggedOutViewModel.cs | 19 + .../Quickstart/Account/LoginInputModel.cs | 19 + .../Quickstart/Account/LoginViewModel.cs | 22 + .../Quickstart/Account/LogoutInputModel.cs | 11 + .../Quickstart/Account/LogoutViewModel.cs | 11 + .../Quickstart/Consent/ConsentController.cs | 76 + .../Quickstart/Consent/ConsentInputModel.cs | 16 + .../Quickstart/Consent/ConsentOptions.cs | 16 + .../Quickstart/Consent/ConsentService.cs | 17 +- .../Quickstart/Consent/ConsentViewModel.cs | 19 + .../Consent/ProcessConsentResult.cs | 18 + .../Quickstart/Consent/ScopeViewModel.cs | 16 + .../Diagnostics/DiagnosticsController.cs | 26 + .../Quickstart/Grants/GrantsController.cs | 89 + .../Quickstart/Grants/GrantsViewModel.cs | 26 + .../Quickstart/Home/ErrorViewModel.cs | 13 + .../Quickstart/Home/HomeController.cs | 43 + .../Quickstart/SecurityHeadersAttribute.cs | 56 + .../QuickstartIdentityServer.csproj | 4 +- Sample/deprecated_IdentityServer/Startup.cs | 51 + .../Views/Account/LoggedOut.cshtml | 34 + .../Views/Account/Login.cshtml | 96 + .../Views/Account/Logout.cshtml | 21 + .../Views/Consent/Index.cshtml | 82 + .../Views/Consent/_ScopeListItem.cshtml | 0 .../Views/Diagnostics/Index.cshtml | 21 + .../Views/Grants/Index.cshtml | 79 + .../Views/Home/Index.cshtml | 35 + .../Views/Shared/Error.cshtml | 43 + .../Views/Shared/_Layout.cshtml | 61 + .../Views/Shared/_ValidationSummary.cshtml | 7 + .../Views/_ViewImports.cshtml | 2 + .../Views/_ViewStart.cshtml | 3 + .../tempkey.rsa | 0 .../wwwroot/css/site.css | 82 + .../wwwroot/css/site.less | 0 .../wwwroot/css/site.min.css | 1 + .../wwwroot/favicon.ico | Bin 0 -> 1150 bytes .../wwwroot/icon.jpg | Bin 0 -> 19482 bytes .../wwwroot/icon.png | Bin 0 -> 20796 bytes .../wwwroot/js/signout-redirect.js | 6 + .../wwwroot/lib/bootstrap/css/bootstrap.css | 0 .../lib/bootstrap/css/bootstrap.css.map | 0 .../lib/bootstrap/css/bootstrap.min.css | 0 .../fonts/glyphicons-halflings-regular.eot | Bin .../fonts/glyphicons-halflings-regular.svg | 0 .../fonts/glyphicons-halflings-regular.ttf | Bin .../fonts/glyphicons-halflings-regular.woff | Bin .../fonts/glyphicons-halflings-regular.woff2 | Bin .../wwwroot/lib/bootstrap/js/bootstrap.js | 0 .../wwwroot/lib/bootstrap/js/bootstrap.min.js | 0 .../wwwroot/lib/jquery/jquery.js | 0 .../wwwroot/lib/jquery/jquery.min.js | 0 .../wwwroot/lib/jquery/jquery.min.map | 0 264 files changed, 59093 insertions(+), 987 deletions(-) create mode 100644 Api.Client/Api.Client.csproj create mode 100644 Api.Client/ClientCredentialsTokenRequest.cs create mode 100644 Api.Client/Program.cs create mode 100644 IdentityServer.LdapExtension/LdapUserProfileService.cs create mode 100644 Sample/IdentityServer/Config.cs create mode 100644 Sample/IdentityServer/Configuration/Clients.cs create mode 100644 Sample/IdentityServer/Configuration/ClientsConsole.cs create mode 100644 Sample/IdentityServer/Configuration/ClientsWeb.cs create mode 100644 Sample/IdentityServer/Configuration/Resources.cs create mode 100644 Sample/IdentityServer/Extensions/ExtensionGrantValidator.cs create mode 100644 Sample/IdentityServer/Extensions/HostProfileService.cs create mode 100644 Sample/IdentityServer/Extensions/NoSubjectExtensionGrantValidator.cs create mode 100644 Sample/IdentityServer/Extensions/ParameterizedScopeParser.cs create mode 100644 Sample/IdentityServer/Extensions/ParameterizedScopeTokenRequestValidator.cs create mode 100644 Sample/IdentityServer/Extensions/SameSiteHandlingExtensions.cs create mode 100644 Sample/IdentityServer/Keys/identityserver.test.ecdsa.p12 create mode 100644 Sample/IdentityServer/Keys/identityserver.test.rsa.p12 create mode 100644 Sample/IdentityServer/LocalApiController.cs create mode 100644 Sample/IdentityServer/Quickstart/Account/ExternalController.cs create mode 100644 Sample/IdentityServer/Quickstart/Account/RedirectViewModel.cs create mode 100644 Sample/IdentityServer/Quickstart/Device/DeviceAuthorizationInputModel.cs create mode 100644 Sample/IdentityServer/Quickstart/Device/DeviceAuthorizationViewModel.cs create mode 100644 Sample/IdentityServer/Quickstart/Device/DeviceController.cs create mode 100644 Sample/IdentityServer/Quickstart/Diagnostics/DiagnosticsViewModel.cs create mode 100644 Sample/IdentityServer/Quickstart/Extensions.cs create mode 100644 Sample/IdentityServer/QuickstartIdentityServer412.csproj create mode 100644 Sample/IdentityServer/Views/Account/AccessDenied.cshtml create mode 100644 Sample/IdentityServer/Views/Device/Success.cshtml create mode 100644 Sample/IdentityServer/Views/Device/UserCodeCapture.cshtml create mode 100644 Sample/IdentityServer/Views/Device/UserCodeConfirmation.cshtml create mode 100644 Sample/IdentityServer/Views/Shared/Redirect.cshtml create mode 100644 Sample/IdentityServer/Views/Shared/_Nav.cshtml create mode 100644 Sample/IdentityServer/Views/Shared/_ScopeListItem.cshtml create mode 100644 Sample/IdentityServer/compilerconfig.json create mode 100644 Sample/IdentityServer/compilerconfig.json.defaults create mode 100644 Sample/IdentityServer/wwwroot/css/site.scss create mode 100644 Sample/IdentityServer/wwwroot/js/signin-redirect.js create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/LICENSE create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/README.md create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/dist/css/bootstrap.css create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/dist/js/bootstrap.js create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/dist/js/bootstrap.js.map create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js.map create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_alert.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_badge.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_breadcrumb.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_button-group.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_buttons.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_card.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_carousel.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_close.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_code.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_custom-forms.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_dropdown.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_forms.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_functions.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_grid.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_images.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_input-group.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_jumbotron.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_list-group.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_media.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_mixins.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_modal.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_nav.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_navbar.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_pagination.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_popover.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_print.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_progress.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_reboot.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_root.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_spinners.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_tables.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_toasts.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_tooltip.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_transitions.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_type.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_utilities.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/_variables.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/bootstrap-grid.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/bootstrap-reboot.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/bootstrap.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_alert.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_background-variant.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_badge.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_border-radius.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_box-shadow.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_breakpoints.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_buttons.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_caret.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_clearfix.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_deprecate.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_float.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_forms.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_gradients.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_grid-framework.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_grid.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_hover.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_image.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_list-group.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_lists.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_nav-divider.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_pagination.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_reset-text.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_resize.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_screen-reader.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_size.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_table-row.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_text-emphasis.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_text-hide.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_text-truncate.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_transition.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/mixins/_visibility.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/utilities/_align.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/utilities/_background.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/utilities/_borders.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/utilities/_clearfix.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/utilities/_display.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/utilities/_embed.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/utilities/_flex.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/utilities/_float.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/utilities/_overflow.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/utilities/_position.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/utilities/_screenreaders.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/utilities/_shadows.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/utilities/_sizing.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/utilities/_spacing.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/utilities/_stretched-link.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/utilities/_text.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/utilities/_visibility.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/bootstrap/scss/vendor/_rfs.scss create mode 100644 Sample/IdentityServer/wwwroot/lib/jquery/LICENSE.txt create mode 100644 Sample/IdentityServer/wwwroot/lib/jquery/README.md create mode 100644 Sample/IdentityServer/wwwroot/lib/jquery/dist/jquery.js create mode 100644 Sample/IdentityServer/wwwroot/lib/jquery/dist/jquery.min.js create mode 100644 Sample/IdentityServer/wwwroot/lib/jquery/dist/jquery.min.map create mode 100644 Sample/IdentityServer/wwwroot/lib/jquery/dist/jquery.slim.js create mode 100644 Sample/IdentityServer/wwwroot/lib/jquery/dist/jquery.slim.min.js create mode 100644 Sample/IdentityServer/wwwroot/lib/jquery/dist/jquery.slim.min.map create mode 100644 Sample/deprecated_IdentityServer/AppSettings.json rename Sample/{IdentityServer => deprecated_IdentityServer}/Data/InMemoryInitConfig.cs (100%) create mode 100644 Sample/deprecated_IdentityServer/Program.cs create mode 100644 Sample/deprecated_IdentityServer/Quickstart/Account/AccountController.cs create mode 100644 Sample/deprecated_IdentityServer/Quickstart/Account/AccountOptions.cs rename Sample/{IdentityServer => deprecated_IdentityServer}/Quickstart/Account/AccountService.cs (100%) create mode 100644 Sample/deprecated_IdentityServer/Quickstart/Account/ExternalProvider.cs create mode 100644 Sample/deprecated_IdentityServer/Quickstart/Account/LoggedOutViewModel.cs create mode 100644 Sample/deprecated_IdentityServer/Quickstart/Account/LoginInputModel.cs create mode 100644 Sample/deprecated_IdentityServer/Quickstart/Account/LoginViewModel.cs create mode 100644 Sample/deprecated_IdentityServer/Quickstart/Account/LogoutInputModel.cs create mode 100644 Sample/deprecated_IdentityServer/Quickstart/Account/LogoutViewModel.cs create mode 100644 Sample/deprecated_IdentityServer/Quickstart/Consent/ConsentController.cs create mode 100644 Sample/deprecated_IdentityServer/Quickstart/Consent/ConsentInputModel.cs create mode 100644 Sample/deprecated_IdentityServer/Quickstart/Consent/ConsentOptions.cs rename Sample/{IdentityServer => deprecated_IdentityServer}/Quickstart/Consent/ConsentService.cs (93%) create mode 100644 Sample/deprecated_IdentityServer/Quickstart/Consent/ConsentViewModel.cs create mode 100644 Sample/deprecated_IdentityServer/Quickstart/Consent/ProcessConsentResult.cs create mode 100644 Sample/deprecated_IdentityServer/Quickstart/Consent/ScopeViewModel.cs create mode 100644 Sample/deprecated_IdentityServer/Quickstart/Diagnostics/DiagnosticsController.cs create mode 100644 Sample/deprecated_IdentityServer/Quickstart/Grants/GrantsController.cs create mode 100644 Sample/deprecated_IdentityServer/Quickstart/Grants/GrantsViewModel.cs create mode 100644 Sample/deprecated_IdentityServer/Quickstart/Home/ErrorViewModel.cs create mode 100644 Sample/deprecated_IdentityServer/Quickstart/Home/HomeController.cs create mode 100644 Sample/deprecated_IdentityServer/Quickstart/SecurityHeadersAttribute.cs rename Sample/{IdentityServer => deprecated_IdentityServer}/QuickstartIdentityServer.csproj (73%) create mode 100644 Sample/deprecated_IdentityServer/Startup.cs create mode 100644 Sample/deprecated_IdentityServer/Views/Account/LoggedOut.cshtml create mode 100644 Sample/deprecated_IdentityServer/Views/Account/Login.cshtml create mode 100644 Sample/deprecated_IdentityServer/Views/Account/Logout.cshtml create mode 100644 Sample/deprecated_IdentityServer/Views/Consent/Index.cshtml rename Sample/{IdentityServer => deprecated_IdentityServer}/Views/Consent/_ScopeListItem.cshtml (100%) create mode 100644 Sample/deprecated_IdentityServer/Views/Diagnostics/Index.cshtml create mode 100644 Sample/deprecated_IdentityServer/Views/Grants/Index.cshtml create mode 100644 Sample/deprecated_IdentityServer/Views/Home/Index.cshtml create mode 100644 Sample/deprecated_IdentityServer/Views/Shared/Error.cshtml create mode 100644 Sample/deprecated_IdentityServer/Views/Shared/_Layout.cshtml create mode 100644 Sample/deprecated_IdentityServer/Views/Shared/_ValidationSummary.cshtml create mode 100644 Sample/deprecated_IdentityServer/Views/_ViewImports.cshtml create mode 100644 Sample/deprecated_IdentityServer/Views/_ViewStart.cshtml rename Sample/{IdentityServer => deprecated_IdentityServer}/tempkey.rsa (100%) create mode 100644 Sample/deprecated_IdentityServer/wwwroot/css/site.css rename Sample/{IdentityServer => deprecated_IdentityServer}/wwwroot/css/site.less (100%) create mode 100644 Sample/deprecated_IdentityServer/wwwroot/css/site.min.css create mode 100644 Sample/deprecated_IdentityServer/wwwroot/favicon.ico create mode 100644 Sample/deprecated_IdentityServer/wwwroot/icon.jpg create mode 100644 Sample/deprecated_IdentityServer/wwwroot/icon.png create mode 100644 Sample/deprecated_IdentityServer/wwwroot/js/signout-redirect.js rename Sample/{IdentityServer => deprecated_IdentityServer}/wwwroot/lib/bootstrap/css/bootstrap.css (100%) rename Sample/{IdentityServer => deprecated_IdentityServer}/wwwroot/lib/bootstrap/css/bootstrap.css.map (100%) rename Sample/{IdentityServer => deprecated_IdentityServer}/wwwroot/lib/bootstrap/css/bootstrap.min.css (100%) rename Sample/{IdentityServer => deprecated_IdentityServer}/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.eot (100%) rename Sample/{IdentityServer => deprecated_IdentityServer}/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.svg (100%) rename Sample/{IdentityServer => deprecated_IdentityServer}/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.ttf (100%) rename Sample/{IdentityServer => deprecated_IdentityServer}/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff (100%) rename Sample/{IdentityServer => deprecated_IdentityServer}/wwwroot/lib/bootstrap/fonts/glyphicons-halflings-regular.woff2 (100%) rename Sample/{IdentityServer => deprecated_IdentityServer}/wwwroot/lib/bootstrap/js/bootstrap.js (100%) rename Sample/{IdentityServer => deprecated_IdentityServer}/wwwroot/lib/bootstrap/js/bootstrap.min.js (100%) rename Sample/{IdentityServer => deprecated_IdentityServer}/wwwroot/lib/jquery/jquery.js (100%) rename Sample/{IdentityServer => deprecated_IdentityServer}/wwwroot/lib/jquery/jquery.min.js (100%) rename Sample/{IdentityServer => deprecated_IdentityServer}/wwwroot/lib/jquery/jquery.min.map (100%) diff --git a/Api.Client/Api.Client.csproj b/Api.Client/Api.Client.csproj new file mode 100644 index 0000000..56b787a --- /dev/null +++ b/Api.Client/Api.Client.csproj @@ -0,0 +1,12 @@ + + + + Exe + net5.0 + + + + + + + diff --git a/Api.Client/ClientCredentialsTokenRequest.cs b/Api.Client/ClientCredentialsTokenRequest.cs new file mode 100644 index 0000000..f6f6f19 --- /dev/null +++ b/Api.Client/ClientCredentialsTokenRequest.cs @@ -0,0 +1,10 @@ +namespace Api.Client +{ + internal class ClientCredentialsTokenRequest + { + public object Address { get; set; } + public string ClientId { get; set; } + public string ClientSecret { get; set; } + public string Scope { get; set; } + } +} \ No newline at end of file diff --git a/Api.Client/Program.cs b/Api.Client/Program.cs new file mode 100644 index 0000000..d09f3f1 --- /dev/null +++ b/Api.Client/Program.cs @@ -0,0 +1,63 @@ +using IdentityModel.Client; +using Newtonsoft.Json.Linq; +using System; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Api.Client +{ + class Program + { + private static async Task Main() + { + Console.WriteLine("Push a key when ready"); + Console.ReadKey(); + // discover endpoints from metadata + var client = new HttpClient(); + + var disco = await client.GetDiscoveryDocumentAsync("https://localhost:5001"); + if (disco.IsError) + { + Console.WriteLine(disco.Error); + return; + } + + // request token + var tokenResponse = await client.RequestClientCredentialsTokenAsync(new IdentityModel.Client.ClientCredentialsTokenRequest + { + Address = disco.TokenEndpoint, + ClientId = "client", + ClientSecret = "secret", + + Scope = "api1" + }); + + if (tokenResponse.IsError) + { + Console.WriteLine(tokenResponse.Error); + return; + } + + Console.WriteLine(tokenResponse.Json); + Console.WriteLine("\n\n"); + + // call api + var apiClient = new HttpClient(); + apiClient.SetBearerToken(tokenResponse.AccessToken); + + var response = await apiClient.GetAsync("https://localhost:5101/identity"); + if (!response.IsSuccessStatusCode) + { + Console.WriteLine(response.StatusCode); + } + else + { + var content = await response.Content.ReadAsStringAsync(); + Console.WriteLine(JArray.Parse(content)); + } + Console.WriteLine("----------------------------------"); + Console.WriteLine("Push any key to close the console app"); + Console.ReadKey(); + } + } +} diff --git a/IdentityServer.LdapExtension.Unit/IdentityServer.LdapExtension.Unit.csproj b/IdentityServer.LdapExtension.Unit/IdentityServer.LdapExtension.Unit.csproj index 67b299d..b02eb9f 100644 --- a/IdentityServer.LdapExtension.Unit/IdentityServer.LdapExtension.Unit.csproj +++ b/IdentityServer.LdapExtension.Unit/IdentityServer.LdapExtension.Unit.csproj @@ -1,16 +1,16 @@  - netcoreapp3.1 + net5.0 false - - + + - + all runtime; build; native; contentfiles; analyzers diff --git a/IdentityServer.LdapExtension.sln b/IdentityServer.LdapExtension.sln index d715cc8..9a2f2f7 100644 --- a/IdentityServer.LdapExtension.sln +++ b/IdentityServer.LdapExtension.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27130.2026 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31025.194 MinimumVisualStudioVersion = 15.0.26124.0 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IdentityServer.LdapExtension", "IdentityServer.LdapExtension\IdentityServer.LdapExtension.csproj", "{73D255AA-0FF3-4861-9397-C19E9E662934}" EndProject @@ -10,12 +10,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MvcVueClient", "MvcVueClien EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MvcClient", "Sample\Client\MvcClient.csproj", "{6525DABA-318A-4185-97C7-1D80002719DA}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuickstartIdentityServer", "Sample\IdentityServer\QuickstartIdentityServer.csproj", "{02103F09-C7AD-4EF0-BF3D-592541E1071B}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Api", "Sample\Api\Api.csproj", "{6DF30367-2BDD-4DB7-82E3-7CDF9A0B3FF4}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sample", "Sample", "{A5A7BF38-9048-4D87-A51C-6A89D3339E1D}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QuickstartIdentityServer412", "Sample\IdentityServer\QuickstartIdentityServer412.csproj", "{AF4A8A19-03BB-4C7C-A0E3-DC46FD5781FB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Api.Client", "Api.Client\Api.Client.csproj", "{254C61F7-B232-4CFF-97B5-D4CD328A4ABB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -74,18 +76,6 @@ Global {6525DABA-318A-4185-97C7-1D80002719DA}.Release|x64.Build.0 = Release|Any CPU {6525DABA-318A-4185-97C7-1D80002719DA}.Release|x86.ActiveCfg = Release|Any CPU {6525DABA-318A-4185-97C7-1D80002719DA}.Release|x86.Build.0 = Release|Any CPU - {02103F09-C7AD-4EF0-BF3D-592541E1071B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {02103F09-C7AD-4EF0-BF3D-592541E1071B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {02103F09-C7AD-4EF0-BF3D-592541E1071B}.Debug|x64.ActiveCfg = Debug|Any CPU - {02103F09-C7AD-4EF0-BF3D-592541E1071B}.Debug|x64.Build.0 = Debug|Any CPU - {02103F09-C7AD-4EF0-BF3D-592541E1071B}.Debug|x86.ActiveCfg = Debug|Any CPU - {02103F09-C7AD-4EF0-BF3D-592541E1071B}.Debug|x86.Build.0 = Debug|Any CPU - {02103F09-C7AD-4EF0-BF3D-592541E1071B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {02103F09-C7AD-4EF0-BF3D-592541E1071B}.Release|Any CPU.Build.0 = Release|Any CPU - {02103F09-C7AD-4EF0-BF3D-592541E1071B}.Release|x64.ActiveCfg = Release|Any CPU - {02103F09-C7AD-4EF0-BF3D-592541E1071B}.Release|x64.Build.0 = Release|Any CPU - {02103F09-C7AD-4EF0-BF3D-592541E1071B}.Release|x86.ActiveCfg = Release|Any CPU - {02103F09-C7AD-4EF0-BF3D-592541E1071B}.Release|x86.Build.0 = Release|Any CPU {6DF30367-2BDD-4DB7-82E3-7CDF9A0B3FF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6DF30367-2BDD-4DB7-82E3-7CDF9A0B3FF4}.Debug|Any CPU.Build.0 = Debug|Any CPU {6DF30367-2BDD-4DB7-82E3-7CDF9A0B3FF4}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -98,6 +88,30 @@ Global {6DF30367-2BDD-4DB7-82E3-7CDF9A0B3FF4}.Release|x64.Build.0 = Release|Any CPU {6DF30367-2BDD-4DB7-82E3-7CDF9A0B3FF4}.Release|x86.ActiveCfg = Release|Any CPU {6DF30367-2BDD-4DB7-82E3-7CDF9A0B3FF4}.Release|x86.Build.0 = Release|Any CPU + {AF4A8A19-03BB-4C7C-A0E3-DC46FD5781FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF4A8A19-03BB-4C7C-A0E3-DC46FD5781FB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF4A8A19-03BB-4C7C-A0E3-DC46FD5781FB}.Debug|x64.ActiveCfg = Debug|Any CPU + {AF4A8A19-03BB-4C7C-A0E3-DC46FD5781FB}.Debug|x64.Build.0 = Debug|Any CPU + {AF4A8A19-03BB-4C7C-A0E3-DC46FD5781FB}.Debug|x86.ActiveCfg = Debug|Any CPU + {AF4A8A19-03BB-4C7C-A0E3-DC46FD5781FB}.Debug|x86.Build.0 = Debug|Any CPU + {AF4A8A19-03BB-4C7C-A0E3-DC46FD5781FB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF4A8A19-03BB-4C7C-A0E3-DC46FD5781FB}.Release|Any CPU.Build.0 = Release|Any CPU + {AF4A8A19-03BB-4C7C-A0E3-DC46FD5781FB}.Release|x64.ActiveCfg = Release|Any CPU + {AF4A8A19-03BB-4C7C-A0E3-DC46FD5781FB}.Release|x64.Build.0 = Release|Any CPU + {AF4A8A19-03BB-4C7C-A0E3-DC46FD5781FB}.Release|x86.ActiveCfg = Release|Any CPU + {AF4A8A19-03BB-4C7C-A0E3-DC46FD5781FB}.Release|x86.Build.0 = Release|Any CPU + {254C61F7-B232-4CFF-97B5-D4CD328A4ABB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {254C61F7-B232-4CFF-97B5-D4CD328A4ABB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {254C61F7-B232-4CFF-97B5-D4CD328A4ABB}.Debug|x64.ActiveCfg = Debug|Any CPU + {254C61F7-B232-4CFF-97B5-D4CD328A4ABB}.Debug|x64.Build.0 = Debug|Any CPU + {254C61F7-B232-4CFF-97B5-D4CD328A4ABB}.Debug|x86.ActiveCfg = Debug|Any CPU + {254C61F7-B232-4CFF-97B5-D4CD328A4ABB}.Debug|x86.Build.0 = Debug|Any CPU + {254C61F7-B232-4CFF-97B5-D4CD328A4ABB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {254C61F7-B232-4CFF-97B5-D4CD328A4ABB}.Release|Any CPU.Build.0 = Release|Any CPU + {254C61F7-B232-4CFF-97B5-D4CD328A4ABB}.Release|x64.ActiveCfg = Release|Any CPU + {254C61F7-B232-4CFF-97B5-D4CD328A4ABB}.Release|x64.Build.0 = Release|Any CPU + {254C61F7-B232-4CFF-97B5-D4CD328A4ABB}.Release|x86.ActiveCfg = Release|Any CPU + {254C61F7-B232-4CFF-97B5-D4CD328A4ABB}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -105,8 +119,9 @@ Global GlobalSection(NestedProjects) = preSolution {12F22F66-839A-43A5-9C2E-BA04268CFB4F} = {A5A7BF38-9048-4D87-A51C-6A89D3339E1D} {6525DABA-318A-4185-97C7-1D80002719DA} = {A5A7BF38-9048-4D87-A51C-6A89D3339E1D} - {02103F09-C7AD-4EF0-BF3D-592541E1071B} = {A5A7BF38-9048-4D87-A51C-6A89D3339E1D} {6DF30367-2BDD-4DB7-82E3-7CDF9A0B3FF4} = {A5A7BF38-9048-4D87-A51C-6A89D3339E1D} + {AF4A8A19-03BB-4C7C-A0E3-DC46FD5781FB} = {A5A7BF38-9048-4D87-A51C-6A89D3339E1D} + {254C61F7-B232-4CFF-97B5-D4CD328A4ABB} = {A5A7BF38-9048-4D87-A51C-6A89D3339E1D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {21CD9FE1-C93F-4EB9-9111-AF1C5B33CBA6} diff --git a/IdentityServer.LdapExtension/DirectoryServices/ILdapConnection.cs b/IdentityServer.LdapExtension/DirectoryServices/ILdapConnection.cs index 779b9fc..4cdf00a 100644 --- a/IdentityServer.LdapExtension/DirectoryServices/ILdapConnection.cs +++ b/IdentityServer.LdapExtension/DirectoryServices/ILdapConnection.cs @@ -5,6 +5,5 @@ /// internal interface ILdapConnection: Novell.Directory.Ldap.ILdapConnection { - } } diff --git a/IdentityServer.LdapExtension/Extensions/EnumExtension.cs b/IdentityServer.LdapExtension/Extensions/EnumExtension.cs index 8e29d4c..644ff71 100644 --- a/IdentityServer.LdapExtension/Extensions/EnumExtension.cs +++ b/IdentityServer.LdapExtension/Extensions/EnumExtension.cs @@ -19,7 +19,9 @@ public static string[] Descriptions get { if (!typeof(T).IsEnum) + { throw new ArgumentException("T must be an enumerated type"); + } List result = new List(); foreach (var e in Enum.GetValues(typeof(T))) diff --git a/IdentityServer.LdapExtension/IdentityServer.LdapExtension.csproj b/IdentityServer.LdapExtension/IdentityServer.LdapExtension.csproj index 977bcac..b4a3db9 100644 --- a/IdentityServer.LdapExtension/IdentityServer.LdapExtension.csproj +++ b/IdentityServer.LdapExtension/IdentityServer.LdapExtension.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net5.0 Nordès Ménard-Lamarre 2.1.14 HoNoSoFt @@ -23,10 +23,10 @@ - - - - + + + + diff --git a/IdentityServer.LdapExtension/LdapService.cs b/IdentityServer.LdapExtension/LdapService.cs index 53d08b7..5fb851d 100644 --- a/IdentityServer.LdapExtension/LdapService.cs +++ b/IdentityServer.LdapExtension/LdapService.cs @@ -158,38 +158,33 @@ public TUser FindUser(string username, string domain) // Could become async foreach (var matchConfig in allSearcheable) { - using(var ldapConnection = new LdapConnection { - SecureSocketLayer = matchConfig.Ssl - }) + using var ldapConnection = new LdapConnection { SecureSocketLayer = matchConfig.Ssl }; + ldapConnection.Connect(matchConfig.Url, matchConfig.FinalLdapConnectionPort); + ldapConnection.Bind(matchConfig.BindDn, matchConfig.BindCredentials); + + var attributes = new TUser().LdapAttributes; + var extrafieldList = new List(); + + if (matchConfig.ExtraAttributes != null) { - ldapConnection.Connect(matchConfig.Url, matchConfig.FinalLdapConnectionPort); - ldapConnection.Bind(matchConfig.BindDn, matchConfig.BindCredentials); - var attributes = new TUser().LdapAttributes; - - var extrafieldList = new List(); + extrafieldList.AddRange(matchConfig.ExtraAttributes); + } - - if(matchConfig.ExtraAttributes != null) - { - extrafieldList.AddRange(matchConfig.ExtraAttributes); - } - - attributes = attributes.Concat(extrafieldList).ToArray(); + attributes = attributes.Concat(extrafieldList).ToArray(); - var searchFilter = string.Format(matchConfig.SearchFilter, username); - var result = ldapConnection.Search( - matchConfig.SearchBase, - LdapConnection.ScopeSub, - searchFilter, - attributes, - false - ); + var searchFilter = string.Format(matchConfig.SearchFilter, username); + var result = ldapConnection.Search( + matchConfig.SearchBase, + LdapConnection.ScopeSub, + searchFilter, + attributes, + false + ); - if (result.HasMore()) // Count is async (not waiting). The hasMore() always works. - { - return (Results: result as LdapSearchResults, LdapConnection: ldapConnection, matchConfig); - } + if (result.HasMore()) // Count is async (not waiting). The hasMore() always works. + { + return (Results: result as LdapSearchResults, LdapConnection: ldapConnection, matchConfig); } } diff --git a/IdentityServer.LdapExtension/LdapUserProfileService.cs b/IdentityServer.LdapExtension/LdapUserProfileService.cs new file mode 100644 index 0000000..721226b --- /dev/null +++ b/IdentityServer.LdapExtension/LdapUserProfileService.cs @@ -0,0 +1,69 @@ +using IdentityServer.LdapExtension.UserStore; +using IdentityServer4.Extensions; +using IdentityServer4.Models; +using IdentityServer4.Services; +using Microsoft.Extensions.Logging; +using System.Linq; +using System.Threading.Tasks; + +namespace IdentityServer.LdapExtension +{ + public class LdapUserProfileService : IProfileService + { + /// + /// The logger + /// + protected readonly ILogger Logger; + + /// + /// The users + /// + protected readonly ILdapUserStore Users; + + /// + /// Initializes a new instance of the class. + /// + /// The users. + /// The logger. + public LdapUserProfileService(ILdapUserStore users, ILogger logger) + { + Users = users; + Logger = logger; + } + + public virtual Task GetProfileDataAsync(ProfileDataRequestContext context) + { + context.LogProfileRequest(Logger); + + if (context.RequestedClaimTypes.Any()) + { + var user = Users.FindBySubjectId(context.Subject.GetSubjectId()); + if (user != null) + { + context.AddRequestedClaims(user.Claims); + } + } + + context.LogIssuedClaims(Logger); + + return Task.CompletedTask; + } + + /// + /// This method gets called whenever identity server needs to determine if the user is valid or active + /// (e.g. if the user's account has been deactivated since they logged in). + /// (e.g. during token issuance or validation). + /// + /// The context. + /// + public virtual Task IsActiveAsync(IsActiveContext context) + { + Logger.LogDebug("IsActive called from: {caller}", context.Caller); + + var user = Users.FindBySubjectId(context.Subject.GetSubjectId()); + context.IsActive = user?.IsActive == true; + + return Task.CompletedTask; + } + } +} diff --git a/MvcVueClient/MvcVueClient.csproj b/MvcVueClient/MvcVueClient.csproj index ed456be..b21efab 100644 --- a/MvcVueClient/MvcVueClient.csproj +++ b/MvcVueClient/MvcVueClient.csproj @@ -1,12 +1,12 @@ - netcoreapp3.1 + net5.0 true - - - + + + diff --git a/Sample/Api/Api.csproj b/Sample/Api/Api.csproj index 1b7ed86..d99959e 100644 --- a/Sample/Api/Api.csproj +++ b/Sample/Api/Api.csproj @@ -1,7 +1,7 @@  - netcoreapp3.1 + net5.0 diff --git a/Sample/Api/Program.cs b/Sample/Api/Program.cs index ff3b369..93c20ed 100644 --- a/Sample/Api/Program.cs +++ b/Sample/Api/Program.cs @@ -2,9 +2,9 @@ // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. using System; -using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; - +using Microsoft.Extensions.Hosting; + namespace Api { public class Program @@ -13,12 +13,14 @@ public static void Main(string[] args) { Console.Title = "API"; - BuildWebHost(args).Run(); + CreateHostBuilder(args).Build().Run(); } - public static IWebHost BuildWebHost(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseStartup() - .Build(); + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); } } \ No newline at end of file diff --git a/Sample/Api/Startup.cs b/Sample/Api/Startup.cs index 21570e2..bee41a3 100644 --- a/Sample/Api/Startup.cs +++ b/Sample/Api/Startup.cs @@ -3,31 +3,51 @@ using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; - +using Microsoft.IdentityModel.Tokens; + namespace Api { public class Startup { public void ConfigureServices(IServiceCollection services) { - services.AddMvcCore() - .AddAuthorization(); - + services.AddControllers(); + + // accepts any access token issued by identity server services.AddAuthentication("Bearer") - .AddIdentityServerAuthentication(options => + .AddJwtBearer("Bearer", options => { - options.Authority = "http://localhost:5000"; - options.RequireHttpsMetadata = false; - - options.ApiName = "api1"; - }); + options.Authority = "https://localhost:5001"; + + options.TokenValidationParameters = new TokenValidationParameters + { + ValidateAudience = false + }; + }); + + // adds an authorization policy to make sure the token is for scope 'api1' + services.AddAuthorization(options => + { + options.AddPolicy("ApiScope", policy => + { + policy.RequireAuthenticatedUser(); + policy.RequireClaim("scope", "api1"); // Needs to be added in the QuickstartIdentityServer412 ( [.AddInMemoryApiScopes(Config.ApiScopes)] ) + }); + }); } public void Configure(IApplicationBuilder app) { - app.UseAuthentication(); - - app.UseEndpoints(x => x.MapDefaultControllerRoute()); + app.UseRouting(); + + app.UseAuthentication(); + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers() + .RequireAuthorization("ApiScope"); + }); } } } \ No newline at end of file diff --git a/Sample/Client/MvcClient.csproj b/Sample/Client/MvcClient.csproj index 50a99f5..bde44ea 100644 --- a/Sample/Client/MvcClient.csproj +++ b/Sample/Client/MvcClient.csproj @@ -1,13 +1,13 @@  - netcoreapp3.1 + net5.0 true - - + + diff --git a/Sample/Client/Startup.cs b/Sample/Client/Startup.cs index 63a6219..4f1c41c 100644 --- a/Sample/Client/Startup.cs +++ b/Sample/Client/Startup.cs @@ -10,36 +10,75 @@ public class Startup { public void ConfigureServices(IServiceCollection services) { - services.AddMvc(); + //services.AddMvc(); - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + //JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); + + //services.AddAuthentication(options => + // { + // options.DefaultScheme = "Cookies"; + // options.DefaultChallengeScheme = "oidc"; + // }) + // .AddCookie("Cookies") + // .AddOpenIdConnect("oidc", options => + // { + // options.SignInScheme = "Cookies"; + + // options.Authority = "https://localhost:5001"; + // options.RequireHttpsMetadata = false; + + // options.ResponseType = "code id_token"; + + // options.ClientId = "mvc"; + // options.ClientSecret = "secret"; + // options.SaveTokens = true; + // options.GetClaimsFromUserInfoEndpoint = true; + // options.Scope.Add("offline_access"); + // options.SaveTokens = true; + // }); + services.AddControllersWithViews(); + + JwtSecurityTokenHandler.DefaultMapInboundClaims = false; services.AddAuthentication(options => - { - options.DefaultScheme = "Cookies"; - options.DefaultChallengeScheme = "oidc"; - }) - .AddCookie("Cookies") - .AddOpenIdConnect("oidc", options => - { - options.SignInScheme = "Cookies"; - - options.Authority = "https://localhost:5001"; - options.RequireHttpsMetadata = false; - - options.ResponseType = "code id_token"; - - options.ClientId = "mvc"; - options.ClientSecret = "secret"; - options.SaveTokens = true; - options.GetClaimsFromUserInfoEndpoint = true; - options.Scope.Add("offline_access"); - options.SaveTokens = true; - }); + { + options.DefaultScheme = "Cookies"; + options.DefaultChallengeScheme = "oidc"; + }) + .AddCookie("Cookies") + .AddOpenIdConnect("oidc", options => + { + options.Authority = "https://localhost:5001"; + + options.ClientId = "mvc"; + options.ClientSecret = "secret"; + options.ResponseType = "code"; + + options.Scope.Add("api1"); + + options.SaveTokens = true; + }); } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { + //if (env.IsDevelopment()) + //{ + // app.UseDeveloperExceptionPage(); + //} + //else + //{ + // app.UseExceptionHandler("/Home/Error"); + //} + + //app.UseAuthentication(); + + //app.UseStaticFiles(); + //app.UseRouting(); + + //app.UseAuthorization(); + + //app.UseEndpoints(c => c.MapDefaultControllerRoute()); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); @@ -49,14 +88,17 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseExceptionHandler("/Home/Error"); } - app.UseAuthentication(); - app.UseStaticFiles(); - app.UseRouting(); + app.UseRouting(); + app.UseAuthentication(); app.UseAuthorization(); - app.UseEndpoints(c => c.MapDefaultControllerRoute()); + app.UseEndpoints(endpoints => + { + endpoints.MapDefaultControllerRoute() + .RequireAuthorization(); + }); } } } \ No newline at end of file diff --git a/Sample/IdentityServer/AppSettings.json b/Sample/IdentityServer/AppSettings.json index c0a2e75..4c02a5a 100644 --- a/Sample/IdentityServer/AppSettings.json +++ b/Sample/IdentityServer/AppSettings.json @@ -1,60 +1,105 @@ -{ - "IdentityServerLdap": { - // Example: If you use a redis instead of in-memory (See Startup.cs) - //"redis": "localhost:32771,ssl=false", - //"RefreshClaimsInSeconds": 3600, - "Connections": [ - { - "FriendlyName": "OpenLdap-Users", - "Url": "localhost", - "Port": 389, - "Ssl": false, - "BindDn": "cn=ldap-ro,dc=contoso,dc=com", - "BindCredentials": "P@ss1W0Rd!", - "SearchBase": "ou=users,DC=contoso,dc=com", - "SearchFilter": "(&(objectClass=posixAccount)(objectClass=person)(uid={0}))", - "PreFilterRegex": "^(?![a|A]).*$" // not mandatory and will take everything not starting with A - }, - { - "FriendlyName": "OpenLdap-BuzzUsers", - "Url": "localhost", - "Port": 389, - "Ssl": false, - "BindDn": "cn=ldap-ro,dc=contoso,dc=com", - "BindCredentials": "P@ss1W0Rd!", - "SearchBase": "ou=users-buzz,DC=contoso,dc=com", - "SearchFilter": "(&(objectClass=posixAccount)(objectClass=person)(uid={0}))", - "PreFilterRegex": "^([a|A]).*$" // not mandatory and will take everything not starting with A - } - ] - } - - // ------------------------------------------------------------------------- - // Retro-compatibility, in case you want to use only one configuration. - // ------------------------------------------------------------------------- - // Openldap - // ------------------------------------------------------------------------- - //"LdapOpenLdap": { - // "url": "localhost", - // "port": 389, - // "ssl": false, - // "bindDn": "cn=ldap-ro,dc=contoso,dc=com", - // "bindCredentials": "P@ss1W0Rd!", - // "searchBase": "ou=users,DC=contoso,dc=com", - // "searchFilter": "(&(objectClass=posixAccount)(objectClass=person)(uid={0}))" // <== OpenLdap specific (users object are also different than AD) - //}, - // ------------------------------------------------------------------------- - // Active directory - // ------------------------------------------------------------------------- - //"ldapActiveDirectory": { - // "url": "localhost", - // "port": 389, - // "ssl": false, - // "bindDn": "cn=ldap-ro,dc=contoso,dc=com", - // "bindCredentials": "P@ss1W0Rd!", - // "searchBase": "cn=users,dc=contoso,dc=com", - // "searchFilter": "(&(objectClass=user)(objectClass=person)(sAMAccountName={0}))", // <== AD specific (users object are also different than OpenLdap) - // // Example: If you use a redis instead of in-memory - // "redis": "localhost:32771,ssl=false" - //} +{ + "ApiScopes": [ + { + "Name": "IdentityServerApi" + }, + { + "Name": "resource1.scope1" + }, + { + "Name": "resource2.scope1" + }, + { + "Name": "scope3" + }, + { + "Name": "shared.scope" + }, + { + "Name": "transaction", + "DisplayName": "Transaction", + "Description": "A transaction" + } + ], + + "ApiResources": [ + { + "Name": "resource1", + "DisplayName": "Resource #1", + + "Scopes": [ + "resource1.scope1", + "shared.scope" + ] + }, + { + "Name": "resource2", + "DisplayName": "Resource #2", + + "UserClaims": [ + "name", + "email" + ], + + "Scopes": [ + "resource2.scope1", + "shared.scope" + ] + } + ], + + "Clients": [ + { + "ClientId": "machine_client", + "ClientSecrets": [ { "Value": "K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=" } ], + "AllowedGrantTypes": [ "client_credentials" ], + "AllowedScopes": [ "resource1.scope1", "resource1.scope2" ], + "Properties": { "foo": "bar" }, + "Claims": [ + { + "type": "c1", + "value": "c1value" + }, + { + "type": "c2", + "value": "c2value" + } + ] + }, + { + "ClientId": "interactive_client", + "ClientSecrets": [ { "Value": "K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols=" } ], + "AllowedGrantTypes": [ "authorization_code", "client_credentials" ], + "AllowedScopes": [ "openid", "profile", "resource1.scope1", "resource1.scope2" ] + } + ], + "IdentityServerLdap": { + // Example: If you use a redis instead of in-memory (See Startup.cs) + //"redis": "localhost:32771,ssl=false", + //"RefreshClaimsInSeconds": 3600, + "Connections": [ + { + "FriendlyName": "Sample_zflexldapadministrator_com", + "Url": "www.zflexldap.com", + "Port": 389, + "Ssl": false, + "BindDn": "cn=ro_admin,ou=sysadmins,dc=zflexsoftware,dc=com", + "BindCredentials": "zflexpass", + "SearchBase": "ou=users,ou=guests,dc=zflexsoftware,dc=com", + "SearchFilter": "(&(objectClass=person)(uid={0}))" + //"PreFilterRegex": "^(?![a|A]).*$" // not mandatory and will take everything not starting with A + } + //{ + // "FriendlyName": "OpenLdap-BuzzUsers", + // "Url": "localhost", + // "Port": 389, + // "Ssl": false, + // "BindDn": "cn=ldap-ro,dc=contoso,dc=com", + // "BindCredentials": "P@ss1W0Rd!", + // "SearchBase": "ou=users-buzz,DC=contoso,dc=com", + // "SearchFilter": "(&(objectClass=posixAccount)(objectClass=person)(uid={0}))", + // "PreFilterRegex": "^([a|A]).*$" // not mandatory and will take everything not starting with A + //} + ] + } } \ No newline at end of file diff --git a/Sample/IdentityServer/Config.cs b/Sample/IdentityServer/Config.cs new file mode 100644 index 0000000..3430927 --- /dev/null +++ b/Sample/IdentityServer/Config.cs @@ -0,0 +1,105 @@ +using IdentityServer4; +using IdentityServer4.Models; +using System.Collections.Generic; + +namespace QuickstartIdentityServer412 +{ + public class Config + { + public static IEnumerable IdentityResources() + { + return new List + { + new IdentityResources.OpenId(), + new IdentityResources.Profile(), + }; + } + + public static IEnumerable GetApiScope() + { + return new List + { + new ApiScope("api1", "My API") + }; + } + + // clients want to access resources (aka scopes) + public static IEnumerable GetClients() + { + // client credentials client + return new List + { + new Client + { + ClientId = "client", + AllowedGrantTypes = GrantTypes.ClientCredentials, + + ClientSecrets = + { + new Secret("secret".Sha256()) + }, + AllowedScopes = { "api1" } + }, + + // resource owner password grant client + new Client + { + ClientId = "ro.client", + AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, + + ClientSecrets = + { + new Secret("secret".Sha256()) + }, + AllowedScopes = { "api1" } + }, + + // OpenID Connect implicit flow client (MVC) + new Client + { + ClientId = "mvc", + ClientSecrets = { new Secret("secret".Sha256()) }, + + AllowedGrantTypes = GrantTypes.Code, + + // where to redirect to after login + RedirectUris = { "https://localhost:5201/signin-oidc" }, + + // where to redirect to after logout + PostLogoutRedirectUris = { "https://localhost:5201/signout-callback-oidc" }, + + AllowedScopes = new List + { + IdentityServerConstants.StandardScopes.OpenId, + IdentityServerConstants.StandardScopes.Profile, + "api1" + } + }, + + //// OpenID Connect implicit flow client (MVC with vue js) + //new Client + //{ + // ClientId = "mvcvue", + // ClientName = "MVC VueJS Client", + // AllowedGrantTypes = GrantTypes.HybridAndClientCredentials, + + // ClientSecrets = + // { + // new Secret("secret".Sha256()) + // }, + + // RedirectUris = { "http://localhost:5006/signin-oidc" }, + // PostLogoutRedirectUris = { "http://localhost:5006/signout-callback-oidc" }, + + // AllowedScopes = + // { + // IdentityServerConstants.StandardScopes.OpenId, + // IdentityServerConstants.StandardScopes.Profile, + // "api1" + // }, + // AllowOfflineAccess = true + //} + }; + } + } +} diff --git a/Sample/IdentityServer/Configuration/Clients.cs b/Sample/IdentityServer/Configuration/Clients.cs new file mode 100644 index 0000000..44eb0b4 --- /dev/null +++ b/Sample/IdentityServer/Configuration/Clients.cs @@ -0,0 +1,22 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using IdentityServer4.Models; +using System.Collections.Generic; + +namespace IdentityServerHost.Configuration +{ + public static class Clients + { + public static IEnumerable Get() + { + var clients = new List(); + + clients.AddRange(ClientsConsole.Get()); + clients.AddRange(ClientsWeb.Get()); + + return clients; + } + } +} \ No newline at end of file diff --git a/Sample/IdentityServer/Configuration/ClientsConsole.cs b/Sample/IdentityServer/Configuration/ClientsConsole.cs new file mode 100644 index 0000000..0b3dc18 --- /dev/null +++ b/Sample/IdentityServer/Configuration/ClientsConsole.cs @@ -0,0 +1,221 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using System.Collections.Generic; +using IdentityServer4; +using IdentityServer4.Models; + +namespace IdentityServerHost.Configuration +{ + public static class ClientsConsole + { + public static IEnumerable Get() + { + return new List + { + /////////////////////////////////////////// + // Console Client Credentials Flow Sample + ////////////////////////////////////////// + new Client + { + ClientId = "client", + ClientSecrets = { new Secret("secret".Sha256()) }, + AllowedGrantTypes = GrantTypes.ClientCredentials, + AllowedScopes = + { + "resource1.scope1", "resource2.scope1", IdentityServerConstants.LocalApi.ScopeName + } + }, + + /////////////////////////////////////////// + // Console Structured Scope Sample + ////////////////////////////////////////// + new Client + { + ClientId = "parameterized.client", + ClientSecrets = { new Secret("secret".Sha256()) }, + AllowedGrantTypes = GrantTypes.ClientCredentials, + AllowedScopes = { "transaction" } + }, + + /////////////////////////////////////////// + // X509 mTLS Client + ////////////////////////////////////////// + new Client + { + ClientId = "mtls", + ClientSecrets = + { + // new Secret(@"CN=mtls.test, OU=ROO\ballen@roo, O=mkcert development certificate", "mtls.test") + // { + // Type = SecretTypes.X509CertificateName + // }, + new Secret("5D9E9B6B333CD42C99D1DE6175CC0F3EF99DDF68", "mtls.test") + { + Type = IdentityServerConstants.SecretTypes.X509CertificateThumbprint + }, + }, + AccessTokenType = AccessTokenType.Jwt, + AllowedGrantTypes = GrantTypes.ClientCredentials, + AllowedScopes = { "resource1.scope1", "resource2.scope1" } + }, + + /////////////////////////////////////////// + // Console Client Credentials Flow with client JWT assertion + ////////////////////////////////////////// + new Client + { + ClientId = "client.jwt", + ClientSecrets = + { + new Secret + { + Type = IdentityServerConstants.SecretTypes.X509CertificateBase64, + Value = + "MIIEgTCCAumgAwIBAgIQDMMu7l/umJhfEbzJMpcttzANBgkqhkiG9w0BAQsFADCBkzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMTQwMgYDVQQLDCtkb21pbmlja0Bkb21icDE2LmZyaXR6LmJveCAoRG9taW5pY2sgQmFpZXIpMTswOQYDVQQDDDJta2NlcnQgZG9taW5pY2tAZG9tYnAxNi5mcml0ei5ib3ggKERvbWluaWNrIEJhaWVyKTAeFw0xOTA2MDEwMDAwMDBaFw0zMDAxMDMxMjM0MDdaMHAxJzAlBgNVBAoTHm1rY2VydCBkZXZlbG9wbWVudCBjZXJ0aWZpY2F0ZTE0MDIGA1UECwwrZG9taW5pY2tAZG9tYnAxNi5mcml0ei5ib3ggKERvbWluaWNrIEJhaWVyKTEPMA0GA1UEAxMGY2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvNtpipaS8k1zA6w0Aoy8U4l+8zM4jHhhblExf3PULrMR6RauxniTki8p+P8CsZT4V8A4qo+JwsgpLIHrVQrbt9DEhHfBKzxwHqt+GoHt7byTfTtp8A/5nLhYc/5CW4HiR194gVx5+HAlvt+BriMTb1czvTf+H20dj41yUPsN7nMdyRLF+uXapQYMLYnq2BJIDq83mqGwojHk7d+N6GwoO95jlyas7KSoj8/FvfbaqkRNx0446hqPOzFHKc8er8K5VrLp6tVjh8ZJyY0F0dKgx6yWITsL54ctbj/cCyfuGjWEMbS2XXgc+x/xQMnmpfhK1qQAUn9jg5EzF9n6mQomOwIDAQABo3MwcTAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUEMUlw41YsKZQVls3pEG6CrJk4O8wEQYDVR0RBAowCIIGY2xpZW50MA0GCSqGSIb3DQEBCwUAA4IBgQC0TjNY4Q3Wmw7ggamDImV6HUng3WbYGLYbbL2e3myBrjIxGd1Bi8ZyOu8qeUMIRAbZt2YsSX5S8kx0biaVg2zC+aO5eHhEWMwKB66huInXFjI4wtxZ22r+33fg1R0cLuEUePhftOWrbL0MS4YXVyn9HUMWO4WptG9PJdxNw1UbEB8nw3FkVOdAC9RGqiqalSK+E2UT/kUbTIQ1gPSdQ3nh52mre0H/T9+IRqiozJtNK/CQg4NuEV7rUXHnp7Fmigp6RIJ4TCozglspL341y0rV8M7npU1FYZC2UKNr4ed+GOO1n/sF3LbXDlPXwne99CVVn85wjDaevoR7Md0y2KwE9EggLYcViXNehx4YVv/BjfgqxW8NxiKAxP6kPOZE0XdBrZj2rmcDcGOXCzzYpcduKhFyTOpA0K5RNGC3j1KOUjPVlOtLvjASP7udBEYNfH3mgqXAgqNDOEKi2jG9LITv2IyGUsXhTAsKNJ6A6qiDBzDrvPAYDvsfabPq6tRTwjA=" + }, + new Secret + { + Type = IdentityServerConstants.SecretTypes.JsonWebKey, + Value = + "{'e':'AQAB','kid':'ZzAjSnraU3bkWGnnAqLapYGpTyNfLbjbzgAPbbW2GEA','kty':'RSA','n':'wWwQFtSzeRjjerpEM5Rmqz_DsNaZ9S1Bw6UbZkDLowuuTCjBWUax0vBMMxdy6XjEEK4Oq9lKMvx9JzjmeJf1knoqSNrox3Ka0rnxXpNAz6sATvme8p9mTXyp0cX4lF4U2J54xa2_S9NF5QWvpXvBeC4GAJx7QaSw4zrUkrc6XyaAiFnLhQEwKJCwUw4NOqIuYvYp_IXhw-5Ti_icDlZS-282PcccnBeOcX7vc21pozibIdmZJKqXNsL1Ibx5Nkx1F1jLnekJAmdaACDjYRLL_6n3W4wUp19UvzB1lGtXcJKLLkqB6YDiZNu16OSiSprfmrRXvYmvD8m6Fnl5aetgKw'}" + } + }, + AllowedGrantTypes = GrantTypes.ClientCredentials, + AllowedScopes = { "resource1.scope1", "resource2.scope1" } + }, + + /////////////////////////////////////////// + // Custom Grant Sample + ////////////////////////////////////////// + new Client + { + ClientId = "client.custom", + ClientSecrets = { new Secret("secret".Sha256()) }, + AllowedGrantTypes = { "custom", "custom.nosubject" }, + AllowedScopes = { "resource1.scope1", "resource2.scope1" } + }, + + /////////////////////////////////////////// + // Console Resource Owner Flow Sample + ////////////////////////////////////////// + new Client + { + ClientId = "roclient", + ClientSecrets = { new Secret("secret".Sha256()) }, + AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, + AllowOfflineAccess = true, + AllowedScopes = + { + IdentityServerConstants.StandardScopes.OpenId, + "custom.profile", + "resource1.scope1", + "resource2.scope1" + }, + + RefreshTokenUsage = TokenUsage.OneTimeOnly, + AbsoluteRefreshTokenLifetime = 3600 * 24, + SlidingRefreshTokenLifetime = 10, + RefreshTokenExpiration = TokenExpiration.Sliding + }, + + /////////////////////////////////////////// + // Console Public Resource Owner Flow Sample + ////////////////////////////////////////// + new Client + { + ClientId = "roclient.public", + RequireClientSecret = false, + AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, + AllowOfflineAccess = true, + AllowedScopes = + { + IdentityServerConstants.StandardScopes.OpenId, + IdentityServerConstants.StandardScopes.Email, + "resource1.scope1", + "resource2.scope1" + } + }, + + /////////////////////////////////////////// + // Console with PKCE Sample + ////////////////////////////////////////// + new Client + { + ClientId = "console.pkce", + ClientName = "Console with PKCE Sample", + RequireClientSecret = false, + AllowedGrantTypes = GrantTypes.Code, + RequirePkce = true, + RedirectUris = { "http://127.0.0.1" }, + AllowOfflineAccess = true, + AllowedScopes = + { + IdentityServerConstants.StandardScopes.OpenId, + IdentityServerConstants.StandardScopes.Profile, + IdentityServerConstants.StandardScopes.Email, + "resource1.scope1", + "resource2.scope1" + } + }, + /////////////////////////////////////////// + // WinConsole with PKCE Sample + ////////////////////////////////////////// + new Client + { + ClientId = "winconsole", + ClientName = "Windows Console with PKCE Sample", + RequireClientSecret = false, + AllowedGrantTypes = GrantTypes.Code, + RequirePkce = true, + RedirectUris = { "sample-windows-client://callback" }, + RequireConsent = false, + AllowOfflineAccess = true, + AllowedIdentityTokenSigningAlgorithms = { "ES256" }, + AllowedScopes = + { + IdentityServerConstants.StandardScopes.OpenId, + IdentityServerConstants.StandardScopes.Profile, + IdentityServerConstants.StandardScopes.Email, + "resource1.scope1", + "resource2.scope1" + } + }, + + + /////////////////////////////////////////// + // Introspection Client Sample + ////////////////////////////////////////// + new Client + { + ClientId = "roclient.reference", + ClientSecrets = { new Secret("secret".Sha256()) }, + AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, + AllowedScopes = { "resource1.scope1", "resource2.scope1", "scope3" }, + AccessTokenType = AccessTokenType.Reference + }, + + /////////////////////////////////////////// + // Device Flow Sample + ////////////////////////////////////////// + new Client + { + ClientId = "device", + ClientName = "Device Flow Client", + AllowedGrantTypes = GrantTypes.DeviceFlow, + RequireClientSecret = false, + AllowOfflineAccess = true, + AllowedScopes = + { + IdentityServerConstants.StandardScopes.OpenId, + IdentityServerConstants.StandardScopes.Profile, + IdentityServerConstants.StandardScopes.Email, + "resource1.scope1", + "resource2.scope1" + } + } + }; + } + } +} \ No newline at end of file diff --git a/Sample/IdentityServer/Configuration/ClientsWeb.cs b/Sample/IdentityServer/Configuration/ClientsWeb.cs new file mode 100644 index 0000000..444b887 --- /dev/null +++ b/Sample/IdentityServer/Configuration/ClientsWeb.cs @@ -0,0 +1,132 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.using System.Collections.Generic; + +using System.Collections.Generic; +using IdentityServer4; +using IdentityServer4.Models; + +namespace IdentityServerHost.Configuration +{ + public static class ClientsWeb + { + static string[] allowedScopes = + { + IdentityServerConstants.StandardScopes.OpenId, + IdentityServerConstants.StandardScopes.Profile, + IdentityServerConstants.StandardScopes.Email, + "resource1.scope1", + "resource2.scope1", + "transaction" + }; + + public static IEnumerable Get() + { + return new List + { + /////////////////////////////////////////// + // JS OIDC Sample + ////////////////////////////////////////// + new Client + { + ClientId = "js_oidc", + ClientName = "JavaScript OIDC Client", + ClientUri = "http://identityserver.io", + + AllowedGrantTypes = GrantTypes.Code, + RequireClientSecret = false, + + RedirectUris = + { + "https://localhost:44300/index.html", + "https://localhost:44300/callback.html", + "https://localhost:44300/silent.html", + "https://localhost:44300/popup.html" + }, + + PostLogoutRedirectUris = { "https://localhost:44300/index.html" }, + AllowedCorsOrigins = { "https://localhost:44300" }, + + AllowedScopes = allowedScopes + }, + + /////////////////////////////////////////// + // MVC Automatic Token Management Sample + ////////////////////////////////////////// + new Client + { + ClientId = "mvc.tokenmanagement", + + ClientSecrets = + { + new Secret("secret".Sha256()) + }, + + AllowedGrantTypes = GrantTypes.Code, + RequirePkce = true, + + AccessTokenLifetime = 75, + + RedirectUris = { "https://localhost:44301/signin-oidc" }, + FrontChannelLogoutUri = "https://localhost:44301/signout-oidc", + PostLogoutRedirectUris = { "https://localhost:44301/signout-callback-oidc" }, + + AllowOfflineAccess = true, + + AllowedScopes = allowedScopes + }, + + /////////////////////////////////////////// + // MVC Code Flow Sample + ////////////////////////////////////////// + new Client + { + ClientId = "mvc.code", + ClientName = "MVC Code Flow", + ClientUri = "http://identityserver.io", + + ClientSecrets = + { + new Secret("secret".Sha256()) + }, + + RequireConsent = true, + AllowedGrantTypes = GrantTypes.Code, + + RedirectUris = { "https://localhost:44302/signin-oidc" }, + FrontChannelLogoutUri = "https://localhost:44302/signout-oidc", + PostLogoutRedirectUris = { "https://localhost:44302/signout-callback-oidc" }, + + AllowOfflineAccess = true, + + AllowedScopes = allowedScopes + }, + + /////////////////////////////////////////// + // MVC Hybrid Flow Sample (Back Channel logout) + ////////////////////////////////////////// + new Client + { + ClientId = "mvc.hybrid.backchannel", + ClientName = "MVC Hybrid (with BackChannel logout)", + ClientUri = "http://identityserver.io", + + ClientSecrets = + { + new Secret("secret".Sha256()) + }, + + AllowedGrantTypes = GrantTypes.Hybrid, + RequirePkce = false, + + RedirectUris = { "https://localhost:44303/signin-oidc" }, + BackChannelLogoutUri = "https://localhost:44303/logout", + PostLogoutRedirectUris = { "https://localhost:44303/signout-callback-oidc" }, + + AllowOfflineAccess = true, + + AllowedScopes = allowedScopes + } + }; + } + } +} \ No newline at end of file diff --git a/Sample/IdentityServer/Configuration/Resources.cs b/Sample/IdentityServer/Configuration/Resources.cs new file mode 100644 index 0000000..4e0e2df --- /dev/null +++ b/Sample/IdentityServer/Configuration/Resources.cs @@ -0,0 +1,80 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using IdentityModel; +using IdentityServer4.Models; +using System.Collections.Generic; +using static IdentityServer4.IdentityServerConstants; + +namespace IdentityServerHost.Configuration +{ + public class Resources + { + // identity resources represent identity data about a user that can be requested via the scope parameter (OpenID Connect) + public static readonly IEnumerable IdentityResources = + new[] + { + // some standard scopes from the OIDC spec + new IdentityResources.OpenId(), + new IdentityResources.Profile(), + new IdentityResources.Email(), + + // custom identity resource with some consolidated claims + new IdentityResource("custom.profile", new[] { JwtClaimTypes.Name, JwtClaimTypes.Email, "location", JwtClaimTypes.Address }) + }; + + // API scopes represent values that describe scope of access and can be requested by the scope parameter (OAuth) + public static readonly IEnumerable ApiScopes = + new[] + { + // local API scope + new ApiScope(LocalApi.ScopeName), + + // resource specific scopes + new ApiScope("resource1.scope1"), + new ApiScope("resource2.scope1"), + + // a scope without resource association + new ApiScope("scope3"), + + // a scope shared by multiple resources + new ApiScope("shared.scope"), + + // a parameterized scope + new ApiScope("transaction", "Transaction") + { + Description = "Some Transaction" + } + }; + + // API resources are more formal representation of a resource with processing rules and their scopes (if any) + public static readonly IEnumerable ApiResources = + new[] + { + new ApiResource("resource1", "Resource 1") + { + ApiSecrets = { new Secret("secret".Sha256()) }, + + Scopes = { "resource1.scope1", "shared.scope" } + }, + + new ApiResource("resource2", "Resource 2") + { + ApiSecrets = + { + new Secret("secret".Sha256()) + }, + + // additional claims to put into access token + UserClaims = + { + JwtClaimTypes.Name, + JwtClaimTypes.Email + }, + + Scopes = { "resource2.scope1", "shared.scope" } + } + }; + } +} diff --git a/Sample/IdentityServer/Extensions/ExtensionGrantValidator.cs b/Sample/IdentityServer/Extensions/ExtensionGrantValidator.cs new file mode 100644 index 0000000..04d19a8 --- /dev/null +++ b/Sample/IdentityServer/Extensions/ExtensionGrantValidator.cs @@ -0,0 +1,35 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using IdentityServer4.Models; +using IdentityServer4.Validation; +using System.Threading.Tasks; + +namespace IdentityServerHost.Extensions +{ + public class ExtensionGrantValidator : IExtensionGrantValidator + { + public Task ValidateAsync(ExtensionGrantValidationContext context) + { + var credential = context.Request.Raw.Get("custom_credential"); + + if (credential != null) + { + context.Result = new GrantValidationResult(subject: "818727", authenticationMethod: "custom"); + } + else + { + // custom error message + context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "invalid custom credential"); + } + + return Task.CompletedTask; + } + + public string GrantType + { + get { return "custom"; } + } + } +} \ No newline at end of file diff --git a/Sample/IdentityServer/Extensions/HostProfileService.cs b/Sample/IdentityServer/Extensions/HostProfileService.cs new file mode 100644 index 0000000..1ffde33 --- /dev/null +++ b/Sample/IdentityServer/Extensions/HostProfileService.cs @@ -0,0 +1,28 @@ +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using IdentityServer.LdapExtension; +using IdentityServer.LdapExtension.UserStore; +using IdentityServer4.Models; +using Microsoft.Extensions.Logging; + +namespace IdentityServerHost.Extensions +{ + public class HostProfileService : LdapUserProfileService + { + public HostProfileService(ILdapUserStore users, ILogger logger) : base(users, logger) + { + } + + public override async Task GetProfileDataAsync(ProfileDataRequestContext context) + { + await base.GetProfileDataAsync(context); + + var transaction = context.RequestedResources.ParsedScopes.FirstOrDefault(x => x.ParsedName == "transaction"); + if (transaction?.ParsedParameter != null) + { + context.IssuedClaims.Add(new Claim("transaction_id", transaction.ParsedParameter)); + } + } + } +} \ No newline at end of file diff --git a/Sample/IdentityServer/Extensions/NoSubjectExtensionGrantValidator.cs b/Sample/IdentityServer/Extensions/NoSubjectExtensionGrantValidator.cs new file mode 100644 index 0000000..a33cd4f --- /dev/null +++ b/Sample/IdentityServer/Extensions/NoSubjectExtensionGrantValidator.cs @@ -0,0 +1,35 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using IdentityServer4.Models; +using IdentityServer4.Validation; +using System.Threading.Tasks; + +namespace IdentityServerHost.Extensions +{ + public class NoSubjectExtensionGrantValidator : IExtensionGrantValidator + { + public Task ValidateAsync(ExtensionGrantValidationContext context) + { + var credential = context.Request.Raw.Get("custom_credential"); + + if (credential != null) + { + context.Result = new GrantValidationResult(); + } + else + { + // custom error message + context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "invalid custom credential"); + } + + return Task.CompletedTask; + } + + public string GrantType + { + get { return "custom.nosubject"; } + } + } +} \ No newline at end of file diff --git a/Sample/IdentityServer/Extensions/ParameterizedScopeParser.cs b/Sample/IdentityServer/Extensions/ParameterizedScopeParser.cs new file mode 100644 index 0000000..2c81e39 --- /dev/null +++ b/Sample/IdentityServer/Extensions/ParameterizedScopeParser.cs @@ -0,0 +1,47 @@ +using IdentityServer4.Validation; +using Microsoft.Extensions.Logging; +using System; + +namespace IdentityServerHost.Extensions +{ + public class ParameterizedScopeParser : DefaultScopeParser + { + public ParameterizedScopeParser(ILogger logger) : base(logger) + { + } + + public override void ParseScopeValue(ParseScopeContext scopeContext) + { + const string transactionScopeName = "transaction"; + const string separator = ":"; + const string transactionScopePrefix = transactionScopeName + separator; + + var scopeValue = scopeContext.RawValue; + + if (scopeValue.StartsWith(transactionScopePrefix)) + { + // we get in here with a scope like "transaction:something" + var parts = scopeValue.Split(separator, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length == 2) + { + scopeContext.SetParsedValues(transactionScopeName, parts[1]); + } + else + { + scopeContext.SetError("transaction scope missing transaction parameter value"); + } + } + else if (scopeValue != transactionScopeName) + { + // we get in here with a scope not like "transaction" + base.ParseScopeValue(scopeContext); + } + else + { + // we get in here with a scope exactly "transaction", which is to say we're ignoring it + // and not including it in the results + scopeContext.SetIgnore(); + } + } + } +} diff --git a/Sample/IdentityServer/Extensions/ParameterizedScopeTokenRequestValidator.cs b/Sample/IdentityServer/Extensions/ParameterizedScopeTokenRequestValidator.cs new file mode 100644 index 0000000..e4a336f --- /dev/null +++ b/Sample/IdentityServer/Extensions/ParameterizedScopeTokenRequestValidator.cs @@ -0,0 +1,21 @@ +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using IdentityServer4.Validation; + +namespace IdentityServerHost.Extensions +{ + public class ParameterizedScopeTokenRequestValidator : ICustomTokenRequestValidator + { + public Task ValidateAsync(CustomTokenRequestValidationContext context) + { + var transaction = context.Result.ValidatedRequest.ValidatedResources.ParsedScopes.FirstOrDefault(x => x.ParsedName == "transaction"); + if (transaction?.ParsedParameter != null) + { + context.Result.ValidatedRequest.ClientClaims.Add(new Claim(transaction.ParsedName, transaction.ParsedParameter)); + } + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/Sample/IdentityServer/Extensions/SameSiteHandlingExtensions.cs b/Sample/IdentityServer/Extensions/SameSiteHandlingExtensions.cs new file mode 100644 index 0000000..8dcd92e --- /dev/null +++ b/Sample/IdentityServer/Extensions/SameSiteHandlingExtensions.cs @@ -0,0 +1,72 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; + +namespace IdentityServerHost.Extensions +{ + // copied from https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/ + public static class SameSiteHandlingExtensions + { + public static IServiceCollection AddSameSiteCookiePolicy(this IServiceCollection services) + { + services.Configure(options => + { + options.MinimumSameSitePolicy = SameSiteMode.Unspecified; + options.OnAppendCookie = cookieContext => + CheckSameSite(cookieContext.Context, cookieContext.CookieOptions); + options.OnDeleteCookie = cookieContext => + CheckSameSite(cookieContext.Context, cookieContext.CookieOptions); + }); + + return services; + } + + private static void CheckSameSite(HttpContext httpContext, CookieOptions options) + { + if (options.SameSite == SameSiteMode.None) + { + var userAgent = httpContext.Request.Headers["User-Agent"].ToString(); + if (!httpContext.Request.IsHttps || DisallowsSameSiteNone(userAgent)) + { + // For .NET Core < 3.1 set SameSite = (SameSiteMode)(-1) + options.SameSite = SameSiteMode.Unspecified; + } + } + } + + private static bool DisallowsSameSiteNone(string userAgent) + { + // Cover all iOS based browsers here. This includes: + // - Safari on iOS 12 for iPhone, iPod Touch, iPad + // - WkWebview on iOS 12 for iPhone, iPod Touch, iPad + // - Chrome on iOS 12 for iPhone, iPod Touch, iPad + // All of which are broken by SameSite=None, because they use the iOS networking stack + if (userAgent.Contains("CPU iPhone OS 12") || userAgent.Contains("iPad; CPU OS 12")) + { + return true; + } + + // Cover Mac OS X based browsers that use the Mac OS networking stack. This includes: + // - Safari on Mac OS X. + // This does not include: + // - Chrome on Mac OS X + // Because they do not use the Mac OS networking stack. + if (userAgent.Contains("Macintosh; Intel Mac OS X 10_14") && + userAgent.Contains("Version/") && userAgent.Contains("Safari")) + { + return true; + } + + // Cover Chrome 50-69, because some versions are broken by SameSite=None, + // and none in this range require it. + // Note: this covers some pre-Chromium Edge versions, + // but pre-Chromium Edge does not require SameSite=None. + if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6")) + { + return true; + } + + return false; + } + } +} \ No newline at end of file diff --git a/Sample/IdentityServer/Keys/identityserver.test.ecdsa.p12 b/Sample/IdentityServer/Keys/identityserver.test.ecdsa.p12 new file mode 100644 index 0000000000000000000000000000000000000000..5b21ec1bafb4d2936dda7778a692cbdd055d2e2d GIT binary patch literal 2800 zcmV)#f(q;c0Ru3C3b+OdDuzgg_YDCD0ic2ksRV)wr7(gDp)i68j|K@UhDe6@ z4FLxRpn?g2FoFqu0s#Opf(dm72`Yw2hW8Bt2LUh~1_~;MNQUu zg&t*oiblmJLm-f`b8IidmKxTn8Ot}&XhRyhWSTU+! zx{svSK%;T=aX?fK$Va%JtQtgkv2Sb}jAJ8^7GfC@hVY%)hV9)P$v>Kd7z&OK1>nFi zJ8Ee{gtQN&cN9p@Oe1D9f@Yl6jkUfGPJRDJ)lvJNAEBg^_HX;e3$76s_V$A!n$~D_ zua=!!!Oa44-nM(usEiZU;nCJ&xs&C__ds4&u)y^tXf=>`nYDg1R-s&|_vu!Qu>~S` z_)bMs*$si{#$8kUuabZ{P?>43#x{naU%+l&2wVCs0;_(5C{SbGtSSa&EMQsa0&2ul z4@TnlZcYS)%$P{YyRQ)zx)n|+fVYT%DA7fBe9+D7w6%cC86sSD>f{nXzj^>x8q6LJ zK|=9jRcLAF<9pd4osdNhGL>##Yvhwirx%b)t zLcsy8;p#{@)>xDqUjS4Hrm1ELZA#!K3Zcd*`v$TZt!&xV&f!+CZ17xBcgRZcH?;~u z71aAJE2zd$Q*pThVlnxP{5N^S_I_xbS~;!7b57F^*>bI(W;|x zsbU1d4(NC}n-7G_I^BW=_k(~|@NCK(GpoXy*yW6ATZ0rPUqF2qDTK5MY>j%0EnX)P z;A)s8+`eyKkvgSv6tN@MZ>k)Bb2s7fIGr}SU0*bG1Rp84udQ6ghVPk8?bhhKl!U_h zLp6Nro}rjfIB#@qG>f`j36h#KSjMr^!pdp&V`uXEfWuwf-sms73jaV{wE_OSuMkAi z2|=vVdmY2TfNSMFG1&!7J5F!5SsaA2Iw<>oEmu(L!vZ7Ew6vuRAZ3vOKr5+PY1cb; zBm>>An#p{OfhIxyZ8*}dM!qrmhr8}ama+X)z)7S+gSPT-#rotBU0KSk7TzYfD;FyW z+93ixe&gN_pm6`b>kx&yx+z~5^ymb)m{-l`By1t~ z9_~X4CZ8_=O@(YVOF_imga#HcdA_tV1kR1WPVU@ct{_`tVsMa`XA!1I`+O<;Vi`i6y zt=D!ent;aJU|DWZ(!ONwafV*VFj=JbKW?2K&DnfaL?Rx=8svpdD3~4b{(01HsAAc7 zsx~`w#zJ!2C?6Hn3{M>#uGAcV8^~qAXT}1>^lv+&5nbB!m!rUNDwK?H-3jbawpFQ@ zNEfpc#AJJ1pjG!Z6B7SBw17N|MdYN=(p@$l=ju@gVm|>;_ zH9N?nR}o^%x*`rDt8{7C4)RZ>XutnLT|eFbcAg}Xp8)j zCQ&_Isd=sG^fM31aUaMaewhk8UjiJCnjd5T@9ftXOX8@m3l@`oI-I3Xc_%iatr(BX zq??CLaAat5dm>C;b&bT7oV0`Dh6OvHtP$hhmTZN-W+(ncP*`b=^1RkgCCR@@@?A}n z?Fw-?K|QHMt`M)m+XoxXFX~*v<3!-movih1?G$| z!exl_y6esayKp9(%VO!OWKO+RW+Q04rBW@LoJx0G&}JCH9bDT_uAyZ=sx{~^@BQC4 zc#o%$fP#Vy51?_r-S}<;Q`rohGugts?Hg(~NZ>dm$2C1{H=~qr&Q6e!)4)vW`%CF5 zFW)bVs=9-JG0$x0k6Zz6r~1=^8)q{vL+`WqvBB#UYw-OSAJ%-lRpmz|>_*_zxS${5 zz8OdW%Yt|i=`Vu*X&c$4v3`Z!!EN~5DY4A z#bCHb+tNN(ac1)`+VRx*< zm2i=dA3AiDMVH`upgN~0zjjjt(WmS&M-~mMTn%UAR!6?9H>AU&)e^m?sni#HFoFRJ z1_>&LNQUdt0s;sC1c8`?*+p4`19XoXR8YA*h$ztYgk{jUz`}67-o_dh z?(;nY8funYHyLez`In06=(6>_6;s=amR|^d3Tv1Teq1^TUS`SdmN@h_39%gNCb=lq zWI{}GSE>`a7rGbBi~!72und-)J&nSFocq;p!?ilC9~#V(%@(o8aOY*rILVzWO87O? z5d$v)Mq_{i&We}-|5&DHb^AauB`_lf2`Yw2hW8Bt2^BFG1Qh-Aj{vBSz>?1cp=}|P z0GS_v3jr`IFdr}n1_dh)0|FWZ6bRdW3Y$Ig^%LTtE~XY{xf$sG!2}3qtnM_TZQ%7& CuqdPe literal 0 HcmV?d00001 diff --git a/Sample/IdentityServer/Keys/identityserver.test.rsa.p12 b/Sample/IdentityServer/Keys/identityserver.test.rsa.p12 new file mode 100644 index 0000000000000000000000000000000000000000..07a23dee1dc108cc4dbf2c1dd5fa04849baedb00 GIT binary patch literal 4079 zcmVrW6|_X=A5EAGO(b&8UWy2$5{ivA<$2$3VO z=zLzcbN_li7Dl{?!UJX9vB-F#&V5ueFF}Bso;^8?5|?l)Cl{Y1RIIp{;+`DDJ&J@6 z&okckcUyiqIGr8-P7Bbs;)_~pI}foWHm#r7MMjI8ihPKrBBiC;IX3uXcQep9F51OK zCSshjT{ci?N_;IWOKdqzb?5`M$9zsWL&v z&g%wN^XScl7J{@aAI2wiBDPoF7!+|beqqT6So*W9I+3^rb52XcG`090u(nFhG&6k^ zs2e{qS}Oht8THi{vR>!Lo)j2&m3Abew|Ek#SQ3z9qu>&MI3`rr+S`)kxwV_wA}zlo z=V+VCl?H;)Wrk;+36XBJi+lUUVci)r}XK6wW#G(=|99J$6AoQU}dJ*$uVh?{= zzZ7Sn_8ZJMmM{Awrc|2mrD5HMPtUNTtIbDE|CpqQEP{p=9KZZet(2h!0Oi4bItux7 zZ2f8kDDxpFQjGSA+6~2a<`y)f8vx2^pMBdsta?a^b zICZ(P%_7QCD=)4Q8$54)@WxZ(Y@~_SSnswMYSJaD{_&J6^qmk9MDMEl`4D|a9n8i_ zB?1FgoJl6%!Tr%uO3aPLK4_weJ^M_jfD)7L7@ygy*gr>G`=`KvNhOb~qkC29v*`Uf zZXKD<#e$MpX?ZXzclIlpwX5jUs`+Y^T@acheS>#N1W+IGMwq)?Gya}8@TDcg#8CBe z6-(OHyzkRv{MArZCvXL>zO`H#Mk~RaC_H7Qg{i?_EJmO-|+vG78 zN2B-F17Cg6IBG(^Mkb16@TucAFI=J5cc3g{T%?hWo63j=?{f^zJ^?jgZ2@Bu^2>$H z)1VY9JKO2&u~bG7@#P77Pcl>qwN#at<@w|KbEt%Si1)g)O_Kr{O&i|!DGHsw8(++mwsQw3?^fva_UNc6Fm?51%e z?JYD&Wjk_gWkT^N8-U=_`)$4fg<+~Q&|na#GQI^>*qf@ykct#Ur>1NNlc7}9#F z?p^eLwksGd1`=rYUY>hHe+T8Y>BYnT@)FKlS!wh-VoA4H6X*GgJ07Z~VSaoyuMbdM zCEw%%pCoaQ0yBsg{F-WK{l=}W=f9hSy+bzT~ZK?)eTit%8E)+Uu7)~oDYNQGyWx`A>KdHPjXiJ>43T# z(%D+_Ub^agnGxlE%}()oOQO*&>EVG|9*X|gr<^wN+Ed(NSb_qR*)6)(vO6_ZYoMWE z0`u|)NoKq3*(BZEaRN?mKkhCJvhoMFVDpvK3tWe^Vd9tFwu@<5rhsga*a02zA>9gE zkoI1%8lDj$IIA^@xQ`K)NTz6>@{?sV2+4-&1F3dh0A^ zJ?yaVok?~m$7|K7TK`8oi<*AA(08C4s4Bw?&VU1!*2kaS--stgq7uuEy~a{a%a}~m z!$y8oLIjx1$*@5yT5OW>4sR=O?^hd)OhTpq?ETmz8x6ju=Jn~@xXz{tP^a6At(P~&1)ULB%; zUSLavQ-G4tk2w)cVW|(w_ob^L*PO91o4(xlmrM&{e8@z}s#_+cPax=nfdu1$Tk!n^ zS0=(To04nV*TXP};$$Ox8NMkjm1vwGb7e51&u;2C`|G=x#KPUuJz~egVf<6=dio7bk4d z(+OFUh#kTuh!91d8{$U1UNXm)ImHpugw>LvxMER;V z^>MI%uZ2UM6M4`w@jTT&$)4x1FwjiD0~LjWbp2sc3wTqz!c=ZdQ|V_LThS=OVa5z{ z6HKk=EJyK6l6`|b6$F&^N}wCL0Oy&0yQNrPY}|weC`!(h#-E;x!73K&ckVO6yKVsh z&1K>yki7n=)!+vl3~lHZC){qy`44D4<`{(A;^qyLU%;e@pqI~mno&Ye7mD&D0YsV7 zaqrru!Ue3*|4UGV_>6`ct+NRQYk^?vaiOsESMBa?1_o=j3e-g95i}bS4I!F&Uv_~1 zeahC{W<0K&&wX4o4R)hv}#xf=c0Xp#3;oZJk+cnFi|y`!Cnfk*oXi0Mod-QLa*x zFrGKUI}P8}MSIRCog6;w9(@+C?wjdXlSt_e+VAmuUN7|Z|cV$3vm5Wy2#u(@ze zvAi*qB^c5L;EGJyibjhvpcMH+S^uqVi-{O!Wk0OTIY&j^PT!n{FoFd^1_>&LNQU@QNj8^oVGjKCp@a4KFO#KVwX+T5uQR{DcJIUYqvMfA>w@l#P17y3 z?PxMFF|fRR^KGGWLOcOHpS-$07)Pi7#0@YDM=dyDOYu*=#eLoGI*1&cgA0h%(SmL5 zx&%!%!tf@T%bNKsV;T={yG&Owgq(W#=>(iWFfr>W(`%zDIe&t{E~MkQ(a zZ)GQmI9|7e^&l!u&4$!bON)e(qyEFud~*pigeWbzxD`F0wE*ay9*xWakjaX8@O=WC z-w7KB$G3it9jDWPCU(2!RC7_6ljbE6Pv|E8oAL>N=T4|K+ieN0^tYfuZ3aZI|0SN= z_?Dq=B_xpLL{Aqs5lO!{Vh%ZqZ+rApBq{^4eLxHlP#P*7sg&gu{D{!G zyaI7rQgF%{`z(D$Ps?isVAQcvopbVrTg2ELdyb92SiIAgtJH7=SXNc1r-EQ@K_Tn1DzWNJV@)4=Y8C9 zlzvR1Vd^kH$0ZYbz*7L$(c?aoA8f1IFNmkl#4AZufv*+}V-l{tiiZR06CCklr;KJH z%kNDygSPK?ez)S(V;%#BFF~=CEQ~8eE04x?3CEtz%iR>Vtp6k80X>XceNM0quqbyN zm@fPzKh+6u%SmLnxUc10nT>?3DP(qu%jntQlrt%NfDX`xhTtF`2d-^qw^%FdpOAT9 z{>MG%@MIS-Fgw8)5nOfN^YZw6`u6kzH5%;rIQNt+Mb)gvO#F44g4JEtGZ^W9S{s<> zEoW6AD7I2b6OvGP9z6{M`QK)IR`@mH5a{ES_O+S}x%f-3?Lxv@&V6`Xf4 zmFyiebL_g9S(hQzPiPGp*UHv!B3#fPSVbP7@$RD)sEUPe<#fDeqQX0#Z-KvYA$u5X zc6~e`pZF#3+23nA?UTNA1P08I5Ix3k?P4NxCJh?DZB-vY5#la*wmHVT_%1HJJaUJ6 zSS!yvhiTK07=t8D2%+i~@ZJkq-&ags3V33SHC@!C{MCX|VU+?f)i3B@V{*_$+P|lv{0X+9E<8%5@x6$vrV8 zFe3&DDuzgg_YDCF6)_eB6univ-`Ctjrn=2SZf?QhitIe&bucS1A20_71uG5%0vZGq hg#$sDQ%yq|2Jm8%c+lsp3%uNY1PGzLN^>8>AF)iOsZ9U? literal 0 HcmV?d00001 diff --git a/Sample/IdentityServer/LocalApiController.cs b/Sample/IdentityServer/LocalApiController.cs new file mode 100644 index 0000000..c4cef13 --- /dev/null +++ b/Sample/IdentityServer/LocalApiController.cs @@ -0,0 +1,22 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Linq; +using static IdentityServer4.IdentityServerConstants; + +namespace IdentityServerHost +{ + [Route("localApi")] + [Authorize(LocalApi.PolicyName)] + public class LocalApiController : ControllerBase + { + public IActionResult Get() + { + var claims = from c in User.Claims select new { c.Type, c.Value }; + return new JsonResult(claims); + } + } +} \ No newline at end of file diff --git a/Sample/IdentityServer/Program.cs b/Sample/IdentityServer/Program.cs index 577f1ae..53768a8 100644 --- a/Sample/IdentityServer/Program.cs +++ b/Sample/IdentityServer/Program.cs @@ -1,24 +1,65 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -using System; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; - -namespace QuickstartIdentityServer -{ - public class Program - { - public static void Main(string[] args) - { - Console.Title = "IdentityServer & LDAP Integration"; - - BuildWebHost(args).Run(); - } - - public static IWebHost BuildWebHost(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseStartup() - .Build(); - } +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; +using Serilog; +using Serilog.Events; +using Serilog.Sinks.SystemConsole.Themes; +using System; +using System.Diagnostics; + +namespace IdentityServerHost +{ + public class Program + { + public static int Main(string[] args) + { + Console.Title = "IdentityServer4"; + Activity.DefaultIdFormat = ActivityIdFormat.W3C; + + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .MinimumLevel.Override("Microsoft", LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft.Hosting.Lifetime", LogEventLevel.Information) + .MinimumLevel.Override("System", LogEventLevel.Warning) + .MinimumLevel.Override("Microsoft.AspNetCore.Authentication", LogEventLevel.Information) + .Enrich.FromLogContext() + //.WriteTo.File(@"identityserver4_log.txt") + // uncomment to write to Azure diagnostics stream + //.WriteTo.File( + // @"D:\home\LogFiles\Application\identityserver.txt", + // fileSizeLimitBytes: 1_000_000, + // rollOnFileSizeLimit: true, + // shared: true, + // flushToDiskInterval: TimeSpan.FromSeconds(1)) + .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level}] {SourceContext}{NewLine}{Message:lj}{NewLine}{Exception}{NewLine}", theme: AnsiConsoleTheme.Code) + .CreateLogger(); + + try + { + Log.Information("Starting host..."); + CreateHostBuilder(args).Build().Run(); + return 0; + } + catch (Exception ex) + { + Log.Fatal(ex, "Host terminated unexpectedly."); + return 1; + } + finally + { + Log.CloseAndFlush(); + } + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .UseSerilog() + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } } \ No newline at end of file diff --git a/Sample/IdentityServer/Quickstart/Account/AccountController.cs b/Sample/IdentityServer/Quickstart/Account/AccountController.cs index 7f1f2b0..eb7904e 100644 --- a/Sample/IdentityServer/Quickstart/Account/AccountController.cs +++ b/Sample/IdentityServer/Quickstart/Account/AccountController.cs @@ -1,346 +1,368 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. -// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. - -using IdentityModel; -using IdentityServer4.Services; -using IdentityServer4.Stores; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Security.Principal; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication; -using IdentityServer4.Events; -using IdentityServer4.Extensions; -using IdentityServer4.Models; -using IdentityServer.LdapExtension.UserStore; +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using IdentityModel; using IdentityServer.LdapExtension.UserModel; - -namespace IdentityServer4.Quickstart.UI -{ - /// - /// This sample controller implements a typical login/logout/provision workflow for local and external accounts. - /// The login service encapsulates the interactions with the user data store. This data store is in-memory only and cannot be used for production! - /// The interaction service provides a way for the UI to communicate with identityserver for validation and context retrieval - /// - [SecurityHeaders] - public class AccountController : Controller - { - private readonly IIdentityServerInteractionService _interaction; - private readonly IEventService _events; - private readonly AccountService _account; - private readonly ILdapUserStore _userStore; - - public AccountController( - IIdentityServerInteractionService interaction, - IClientStore clientStore, - IHttpContextAccessor httpContextAccessor, - IAuthenticationSchemeProvider schemeProvider, - IEventService events, - ILdapUserStore userStore) - { - _interaction = interaction; - _events = events; - _userStore = userStore; - // Should we modify in the service? good question... - _account = new AccountService(interaction, httpContextAccessor, schemeProvider, clientStore); - } - - /// - /// Show login page - /// - [HttpGet] - public async Task Login(string returnUrl) - { - // build a model so we know what to show on the login page - var vm = await _account.BuildLoginViewModelAsync(returnUrl); - - if (vm.IsExternalLoginOnly) - { - // we only have one option for logging in and it's an external provider - return ExternalLogin(vm.ExternalLoginScheme, returnUrl); - } - - return View(vm); - } - - /// - /// Handle postback from username/password login - /// - [HttpPost] - [ValidateAntiForgeryToken] - public async Task Login(LoginInputModel model, string button) - { - if (button != "login") - { - // the user clicked the "cancel" button - var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); - if (context != null) - { - // if the user cancels, send a result back into IdentityServer as if they - // denied the consent (even if this client does not require consent). - // this will send back an access denied OIDC error response to the client. - await _interaction.GrantConsentAsync(context, ConsentResponse.Denied); - - // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null - return Redirect(model.ReturnUrl); - } - else - { - // since we don't have a valid context, then we just go back to the home page - return Redirect("~/"); - } - } - - if (ModelState.IsValid) - { - // validate username/password against Ldap - var user = _userStore.ValidateCredentials(model.Username, model.Password); - - // In case you have multiple domain entry, you can also choose to do that. - // also, don't forget to uncomment the line 33+ in Login.cshtml - //var user = _userStore.ValidateCredentials(model.Username, model.Password, model.Domain); - if (user != default(IAppUser)) - { - await _events.RaiseAsync(new UserLoginSuccessEvent(user.Username, user.SubjectId, user.Username)); - - // only set explicit expiration here if user chooses "remember me". - // otherwise we rely upon expiration configured in cookie middleware. - AuthenticationProperties props = null; - if (AccountOptions.AllowRememberLogin && model.RememberLogin) - { - props = new AuthenticationProperties - { - IsPersistent = true, - ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration) - }; - }; - - // issue authentication cookie with subject ID and username - await HttpContext.SignInAsync(user.SubjectId, user.Username, props); - - // make sure the returnUrl is still valid, and if so redirect back to authorize endpoint or a local page - if (_interaction.IsValidReturnUrl(model.ReturnUrl) || Url.IsLocalUrl(model.ReturnUrl)) - { - return Redirect(model.ReturnUrl); - } - - return Redirect("~/"); - } - - await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials")); - - ModelState.AddModelError("", AccountOptions.InvalidCredentialsErrorMessage); - } - - // something went wrong, show form with error - var vm = await _account.BuildLoginViewModelAsync(model); - return View(vm); - } - - /// - /// initiate roundtrip to external authentication provider - /// - [HttpGet] - public IActionResult ExternalLogin(string provider, string returnUrl) - { - var props = new AuthenticationProperties() - { - RedirectUri = Url.Action("ExternalLoginCallback"), - Items = - { - { "returnUrl", returnUrl } - } - }; - - // windows authentication needs special handling - // since they don't support the redirect uri, - // so this URL is re-triggered when we call challenge - - // This is Windows only code ... i ignore it at the moment - //if (AccountOptions.WindowsAuthenticationSchemeName == provider) - //{ - // // see if windows auth has already been requested and succeeded - // var result = await HttpContext.AuthenticateAsync(AccountOptions.WindowsAuthenticationSchemeName); - // if (result?.Principal is WindowsPrincipal wp) - // { - // props.Items.Add("scheme", AccountOptions.WindowsAuthenticationSchemeName); - - // var id = new ClaimsIdentity(provider); - // id.AddClaim(new Claim(JwtClaimTypes.Subject, wp.Identity.Name)); - // id.AddClaim(new Claim(JwtClaimTypes.Name, wp.Identity.Name)); - - // // add the groups as claims -- be careful if the number of groups is too large - // if (AccountOptions.IncludeWindowsGroups) - // { - // var wi = wp.Identity as WindowsIdentity; - // var groups = wi.Groups.Translate(typeof(NTAccount)); - // var roles = groups.Select(x => new Claim(JwtClaimTypes.Role, x.Value)); - // id.AddClaims(roles); - // } - - // await HttpContext.SignInAsync( - // IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme, - // new ClaimsPrincipal(id), - // props); - // return Redirect(props.RedirectUri); - // } - // else - // { - // // challenge/trigger windows auth - // return Challenge(AccountOptions.WindowsAuthenticationSchemeName); - // } - //} - //else - { - // start challenge and roundtrip the return URL - props.Items.Add("scheme", provider); - return Challenge(props, provider); - } - } - - /// - /// Post processing of external authentication - /// - [HttpGet] - public async Task ExternalLoginCallback() - { - // read external identity from the temporary cookie - var result = await HttpContext.AuthenticateAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme); - if (result?.Succeeded != true) - { - throw new Exception("External authentication error"); - } - - // retrieve claims of the external user - var externalUser = result.Principal; - var claims = externalUser.Claims.ToList(); - - // try to determine the unique id of the external user (issued by the provider) - // the most common claim type for that are the sub claim and the NameIdentifier - // depending on the external provider, some other claim type might be used - var userIdClaim = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.Subject); - if (userIdClaim == null) - { - userIdClaim = claims.FirstOrDefault(x => x.Type == ClaimTypes.NameIdentifier); - } - if (userIdClaim == null) - { - throw new Exception("Unknown userid"); - } - - // remove the user id claim from the claims collection and move to the userId property - // also set the name of the external authentication provider - claims.Remove(userIdClaim); - var provider = result.Properties.Items["scheme"]; - var userId = userIdClaim.Value; - - // this is where custom logic would most likely be needed to match your users from the - // external provider's authentication result, and provision the user as you see fit. - // - // check if the external user is already provisioned - // - // (This is not necessary adding to your LDAP, in this case we keep it in memory or in Redis as a reference. - // I don't think it's a good idea to update your own Ldap for this except maybe for some scenario. - var user = _userStore.FindByExternalProvider(provider, userId); - if (user == default(IAppUser)) - { - // this sample simply auto-provisions new external user - // another common approach is to start a registrations workflow first - user = _userStore.AutoProvisionUser(provider, userId, claims); - } - - var additionalClaims = new List(); - - // if the external system sent a session id claim, copy it over - // so we can use it for single sign-out - var sid = claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId); - if (sid != null) - { - additionalClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value)); - } - - // if the external provider issued an id_token, we'll keep it for signout - AuthenticationProperties props = null; - var id_token = result.Properties.GetTokenValue("id_token"); - if (id_token != null) - { - props = new AuthenticationProperties(); - props.StoreTokens(new[] { new AuthenticationToken { Name = "id_token", Value = id_token } }); - } - - // issue authentication cookie for user - await _events.RaiseAsync(new UserLoginSuccessEvent(provider, userId, user.SubjectId, user.Username)); - await HttpContext.SignInAsync(user.SubjectId, user.Username, provider, props, additionalClaims.ToArray()); - - // delete temporary cookie used during external authentication - await HttpContext.SignOutAsync(IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme); - - // validate return URL and redirect back to authorization endpoint or a local page - var returnUrl = result.Properties.Items["returnUrl"]; - if (_interaction.IsValidReturnUrl(returnUrl) || Url.IsLocalUrl(returnUrl)) - { - return Redirect(returnUrl); - } - - return Redirect("~/"); - } - - /// - /// Show logout page - /// - [HttpGet] - public async Task Logout(string logoutId) - { - // build a model so the logout page knows what to display - var vm = await _account.BuildLogoutViewModelAsync(logoutId); - - if (vm.ShowLogoutPrompt == false) - { - // if the request for logout was properly authenticated from IdentityServer, then - // we don't need to show the prompt and can just log the user out directly. - return await Logout(vm); - } - - return View(vm); - } - - /// - /// Handle logout page postback - /// - [HttpPost] - [ValidateAntiForgeryToken] - public async Task Logout(LogoutInputModel model) - { - // build a model so the logged out page knows what to display - var vm = await _account.BuildLoggedOutViewModelAsync(model.LogoutId); - - var user = HttpContext.User; - if (user?.Identity.IsAuthenticated == true) - { - // delete local authentication cookie - await HttpContext.SignOutAsync(); - - // raise the logout event - await _events.RaiseAsync(new UserLogoutSuccessEvent(user.GetSubjectId(), user.GetDisplayName())); - } - - // check if we need to trigger sign-out at an upstream identity provider - if (vm.TriggerExternalSignout) - { - // build a return URL so the upstream provider will redirect back - // to us after the user has logged out. this allows us to then - // complete our single sign-out processing. - string url = Url.Action("Logout", new { logoutId = vm.LogoutId }); - - // this triggers a redirect to the external provider for sign-out - return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme); - } - - return View("LoggedOut", vm); - } - } -} \ No newline at end of file +using IdentityServer.LdapExtension.UserStore; +using IdentityServer4; +using IdentityServer4.Events; +using IdentityServer4.Extensions; +using IdentityServer4.Models; +using IdentityServer4.Services; +using IdentityServer4.Stores; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace IdentityServerHost.Quickstart.UI +{ + /// + /// This sample controller implements a typical login/logout/provision workflow for local and external accounts. + /// The login service encapsulates the interactions with the user data store. This data store is in-memory only and cannot be used for production! + /// The interaction service provides a way for the UI to communicate with identityserver for validation and context retrieval + /// + [SecurityHeaders] + [AllowAnonymous] + public class AccountController : Controller + { + private readonly ILdapUserStore _users; + private readonly IIdentityServerInteractionService _interaction; + private readonly IClientStore _clientStore; + private readonly IAuthenticationSchemeProvider _schemeProvider; + private readonly IEventService _events; + + public AccountController( + IIdentityServerInteractionService interaction, + IClientStore clientStore, + IAuthenticationSchemeProvider schemeProvider, + IEventService events, + ILdapUserStore users = null) + { + _users = users; // Should use the LdapStore. + + _interaction = interaction; + _clientStore = clientStore; + _schemeProvider = schemeProvider; + _events = events; + } + + /// + /// Entry point into the login workflow + /// + [HttpGet] + public async Task Login(string returnUrl) + { + // build a model so we know what to show on the login page + var vm = await BuildLoginViewModelAsync(returnUrl); + + if (vm.IsExternalLoginOnly) + { + // we only have one option for logging in and it's an external provider + return RedirectToAction("Challenge", "External", new { scheme = vm.ExternalLoginScheme, returnUrl }); + } + + return View(vm); + } + + /// + /// Handle postback from username/password login + /// + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Login(LoginInputModel model, string button) + { + // check if we are in the context of an authorization request + var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); + + // the user clicked the "cancel" button + if (button != "login") + { + if (context != null) + { + // if the user cancels, send a result back into IdentityServer as if they + // denied the consent (even if this client does not require consent). + // this will send back an access denied OIDC error response to the client. + await _interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied); + + // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null + if (context.IsNativeClient()) + { + // The client is native, so this change in how to + // return the response is for better UX for the end user. + return this.LoadingPage("Redirect", model.ReturnUrl); + } + + return Redirect(model.ReturnUrl); + } + else + { + // since we don't have a valid context, then we just go back to the home page + return Redirect("~/"); + } + } + + if (ModelState.IsValid) + { + // validate username/password against in-memory store + var user = _users.ValidateCredentials(model.Username, model.Password); + if (user != default(IAppUser)) + { + //var user = _users.FindByUsername(model.Username); + await _events.RaiseAsync(new UserLoginSuccessEvent(user.Username, user.SubjectId, user.Username, clientId: context?.Client.ClientId)); + + // only set explicit expiration here if user chooses "remember me". + // otherwise we rely upon expiration configured in cookie middleware. + AuthenticationProperties props = null; + if (AccountOptions.AllowRememberLogin && model.RememberLogin) + { + props = new AuthenticationProperties + { + IsPersistent = true, + ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration) + }; + }; + + // issue authentication cookie with subject ID and username + var isuser = new IdentityServerUser(user.SubjectId) + { + DisplayName = user.Username + }; + + await HttpContext.SignInAsync(isuser, props); + + if (context != null) + { + if (context.IsNativeClient()) + { + // The client is native, so this change in how to + // return the response is for better UX for the end user. + return this.LoadingPage("Redirect", model.ReturnUrl); + } + + // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null + return Redirect(model.ReturnUrl); + } + + // request for a local page + if (Url.IsLocalUrl(model.ReturnUrl)) + { + return Redirect(model.ReturnUrl); + } + else if (string.IsNullOrEmpty(model.ReturnUrl)) + { + return Redirect("~/"); + } + else + { + // user might have clicked on a malicious link - should be logged + throw new Exception("invalid return URL"); + } + } + + await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials", clientId:context?.Client.ClientId)); + ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage); + } + + // something went wrong, show form with error + var vm = await BuildLoginViewModelAsync(model); + return View(vm); + } + + + /// + /// Show logout page + /// + [HttpGet] + public async Task Logout(string logoutId) + { + // build a model so the logout page knows what to display + var vm = await BuildLogoutViewModelAsync(logoutId); + + if (vm.ShowLogoutPrompt == false) + { + // if the request for logout was properly authenticated from IdentityServer, then + // we don't need to show the prompt and can just log the user out directly. + return await Logout(vm); + } + + return View(vm); + } + + /// + /// Handle logout page postback + /// + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Logout(LogoutInputModel model) + { + // build a model so the logged out page knows what to display + var vm = await BuildLoggedOutViewModelAsync(model.LogoutId); + + if (User?.Identity.IsAuthenticated == true) + { + // delete local authentication cookie + await HttpContext.SignOutAsync(); + + // raise the logout event + await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName())); + } + + // check if we need to trigger sign-out at an upstream identity provider + if (vm.TriggerExternalSignout) + { + // build a return URL so the upstream provider will redirect back + // to us after the user has logged out. this allows us to then + // complete our single sign-out processing. + string url = Url.Action("Logout", new { logoutId = vm.LogoutId }); + + // this triggers a redirect to the external provider for sign-out + return SignOut(new AuthenticationProperties { RedirectUri = url }, vm.ExternalAuthenticationScheme); + } + + return View("LoggedOut", vm); + } + + [HttpGet] + public IActionResult AccessDenied() + { + return View(); + } + + + /*****************************************/ + /* helper APIs for the AccountController */ + /*****************************************/ + private async Task BuildLoginViewModelAsync(string returnUrl) + { + var context = await _interaction.GetAuthorizationContextAsync(returnUrl); + if (context?.IdP != null && await _schemeProvider.GetSchemeAsync(context.IdP) != null) + { + var local = context.IdP == IdentityServer4.IdentityServerConstants.LocalIdentityProvider; + + // this is meant to short circuit the UI and only trigger the one external IdP + var vm = new LoginViewModel + { + EnableLocalLogin = local, + ReturnUrl = returnUrl, + Username = context?.LoginHint, + }; + + if (!local) + { + vm.ExternalProviders = new[] { new ExternalProvider { AuthenticationScheme = context.IdP } }; + } + + return vm; + } + + var schemes = await _schemeProvider.GetAllSchemesAsync(); + + var providers = schemes + .Where(x => x.DisplayName != null) + .Select(x => new ExternalProvider + { + DisplayName = x.DisplayName ?? x.Name, + AuthenticationScheme = x.Name + }).ToList(); + + var allowLocal = true; + if (context?.Client.ClientId != null) + { + var client = await _clientStore.FindEnabledClientByIdAsync(context.Client.ClientId); + if (client != null) + { + allowLocal = client.EnableLocalLogin; + + if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any()) + { + providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList(); + } + } + } + + return new LoginViewModel + { + AllowRememberLogin = AccountOptions.AllowRememberLogin, + EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin, + ReturnUrl = returnUrl, + Username = context?.LoginHint, + ExternalProviders = providers.ToArray() + }; + } + + private async Task BuildLoginViewModelAsync(LoginInputModel model) + { + var vm = await BuildLoginViewModelAsync(model.ReturnUrl); + vm.Username = model.Username; + vm.RememberLogin = model.RememberLogin; + return vm; + } + + private async Task BuildLogoutViewModelAsync(string logoutId) + { + var vm = new LogoutViewModel { LogoutId = logoutId, ShowLogoutPrompt = AccountOptions.ShowLogoutPrompt }; + + if (User?.Identity.IsAuthenticated != true) + { + // if the user is not authenticated, then just show logged out page + vm.ShowLogoutPrompt = false; + return vm; + } + + var context = await _interaction.GetLogoutContextAsync(logoutId); + if (context?.ShowSignoutPrompt == false) + { + // it's safe to automatically sign-out + vm.ShowLogoutPrompt = false; + return vm; + } + + // show the logout prompt. this prevents attacks where the user + // is automatically signed out by another malicious web page. + return vm; + } + + private async Task BuildLoggedOutViewModelAsync(string logoutId) + { + // get context information (client name, post logout redirect URI and iframe for federated signout) + var logout = await _interaction.GetLogoutContextAsync(logoutId); + + var vm = new LoggedOutViewModel + { + AutomaticRedirectAfterSignOut = AccountOptions.AutomaticRedirectAfterSignOut, + PostLogoutRedirectUri = logout?.PostLogoutRedirectUri, + ClientName = string.IsNullOrEmpty(logout?.ClientName) ? logout?.ClientId : logout?.ClientName, + SignOutIframeUrl = logout?.SignOutIFrameUrl, + LogoutId = logoutId + }; + + if (User?.Identity.IsAuthenticated == true) + { + var idp = User.FindFirst(JwtClaimTypes.IdentityProvider)?.Value; + if (idp != null && idp != IdentityServer4.IdentityServerConstants.LocalIdentityProvider) + { + var providerSupportsSignout = await HttpContext.GetSchemeSupportsSignOutAsync(idp); + if (providerSupportsSignout) + { + if (vm.LogoutId == null) + { + // if there's no current logout context, we need to create one + // this captures necessary info from the current logged in user + // before we signout and redirect away to the external IdP for signout + vm.LogoutId = await _interaction.CreateLogoutContextAsync(); + } + + vm.ExternalAuthenticationScheme = idp; + } + } + } + + return vm; + } + } +} diff --git a/Sample/IdentityServer/Quickstart/Account/AccountOptions.cs b/Sample/IdentityServer/Quickstart/Account/AccountOptions.cs index 25a14fb..552f190 100644 --- a/Sample/IdentityServer/Quickstart/Account/AccountOptions.cs +++ b/Sample/IdentityServer/Quickstart/Account/AccountOptions.cs @@ -4,7 +4,7 @@ using System; -namespace IdentityServer4.Quickstart.UI +namespace IdentityServerHost.Quickstart.UI { public class AccountOptions { @@ -15,13 +15,6 @@ public class AccountOptions public static bool ShowLogoutPrompt = true; public static bool AutomaticRedirectAfterSignOut = false; - // to enable windows authentication, the host (IIS or IIS Express) also must have - // windows auth enabled. - public static bool WindowsAuthenticationEnabled = true; - public static bool IncludeWindowsGroups = false; - // specify the Windows authentication scheme - public static readonly string WindowsAuthenticationSchemeName = Microsoft.AspNetCore.Server.IISIntegration.IISDefaults.AuthenticationScheme; - public static string InvalidCredentialsErrorMessage = "Invalid username or password"; } } diff --git a/Sample/IdentityServer/Quickstart/Account/ExternalController.cs b/Sample/IdentityServer/Quickstart/Account/ExternalController.cs new file mode 100644 index 0000000..931c40b --- /dev/null +++ b/Sample/IdentityServer/Quickstart/Account/ExternalController.cs @@ -0,0 +1,195 @@ +using IdentityModel; +using IdentityServer.LdapExtension.UserModel; +using IdentityServer.LdapExtension.UserStore; +using IdentityServer4; +using IdentityServer4.Events; +using IdentityServer4.Services; +using IdentityServer4.Stores; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; + +namespace IdentityServerHost.Quickstart.UI +{ + [SecurityHeaders] + [AllowAnonymous] + public class ExternalController : Controller + { + private readonly ILdapUserStore _users; + private readonly IIdentityServerInteractionService _interaction; + private readonly IClientStore _clientStore; + private readonly ILogger _logger; + private readonly IEventService _events; + + public ExternalController( + IIdentityServerInteractionService interaction, + IClientStore clientStore, + IEventService events, + ILogger logger, + ILdapUserStore users = null) + { + _users = users; + + _interaction = interaction; + _clientStore = clientStore; + _logger = logger; + _events = events; + } + + /// + /// initiate roundtrip to external authentication provider + /// + [HttpGet] + public IActionResult Challenge(string scheme, string returnUrl) + { + if (string.IsNullOrEmpty(returnUrl)) returnUrl = "~/"; + + // validate returnUrl - either it is a valid OIDC URL or back to a local page + if (Url.IsLocalUrl(returnUrl) == false && _interaction.IsValidReturnUrl(returnUrl) == false) + { + // user might have clicked on a malicious link - should be logged + throw new Exception("invalid return URL"); + } + + // start challenge and roundtrip the return URL and scheme + var props = new AuthenticationProperties + { + RedirectUri = Url.Action(nameof(Callback)), + Items = + { + { "returnUrl", returnUrl }, + { "scheme", scheme }, + } + }; + + return Challenge(props, scheme); + + } + + /// + /// Post processing of external authentication + /// + [HttpGet] + public async Task Callback() + { + // read external identity from the temporary cookie + var result = await HttpContext.AuthenticateAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); + if (result?.Succeeded != true) + { + throw new Exception("External authentication error"); + } + + if (_logger.IsEnabled(LogLevel.Debug)) + { + var externalClaims = result.Principal.Claims.Select(c => $"{c.Type}: {c.Value}"); + _logger.LogDebug("External claims: {@claims}", externalClaims); + } + + // lookup our user and external provider info + var (user, provider, providerUserId, claims) = FindUserFromExternalProvider(result); + if (user == null) + { + // this might be where you might initiate a custom workflow for user registration + // in this sample we don't show how that would be done, as our sample implementation + // simply auto-provisions new external user + user = AutoProvisionUser(provider, providerUserId, claims); + } + + // this allows us to collect any additional claims or properties + // for the specific protocols used and store them in the local auth cookie. + // this is typically used to store data needed for signout from those protocols. + var additionalLocalClaims = new List(); + var localSignInProps = new AuthenticationProperties(); + ProcessLoginCallback(result, additionalLocalClaims, localSignInProps); + + // issue authentication cookie for user + var isuser = new IdentityServerUser(user.SubjectId) + { + DisplayName = user.Username, + IdentityProvider = provider, + AdditionalClaims = additionalLocalClaims + }; + + await HttpContext.SignInAsync(isuser, localSignInProps); + + // delete temporary cookie used during external authentication + await HttpContext.SignOutAsync(IdentityServerConstants.ExternalCookieAuthenticationScheme); + + // retrieve return URL + var returnUrl = result.Properties.Items["returnUrl"] ?? "~/"; + + // check if external login is in the context of an OIDC request + var context = await _interaction.GetAuthorizationContextAsync(returnUrl); + await _events.RaiseAsync(new UserLoginSuccessEvent(provider, providerUserId, user.SubjectId, user.Username, true, context?.Client.ClientId)); + + if (context != null) + { + if (context.IsNativeClient()) + { + // The client is native, so this change in how to + // return the response is for better UX for the end user. + return this.LoadingPage("Redirect", returnUrl); + } + } + + return Redirect(returnUrl); + } + + private (IAppUser user, string provider, string providerUserId, IEnumerable claims) FindUserFromExternalProvider(AuthenticateResult result) + { + var externalUser = result.Principal; + + // try to determine the unique id of the external user (issued by the provider) + // the most common claim type for that are the sub claim and the NameIdentifier + // depending on the external provider, some other claim type might be used + var userIdClaim = externalUser.FindFirst(JwtClaimTypes.Subject) ?? + externalUser.FindFirst(ClaimTypes.NameIdentifier) ?? + throw new Exception("Unknown userid"); + + // remove the user id claim so we don't include it as an extra claim if/when we provision the user + var claims = externalUser.Claims.ToList(); + claims.Remove(userIdClaim); + + var provider = result.Properties.Items["scheme"]; + var providerUserId = userIdClaim.Value; + + // find external user + var user = _users.FindByExternalProvider(provider, providerUserId); + + return (user, provider, providerUserId, claims); + } + + private IAppUser AutoProvisionUser(string provider, string providerUserId, IEnumerable claims) + { + var user = _users.AutoProvisionUser(provider, providerUserId, claims.ToList()); + return user; + } + + // if the external login is OIDC-based, there are certain things we need to preserve to make logout work + // this will be different for WS-Fed, SAML2p or other protocols + private void ProcessLoginCallback(AuthenticateResult externalResult, List localClaims, AuthenticationProperties localSignInProps) + { + // if the external system sent a session id claim, copy it over + // so we can use it for single sign-out + var sid = externalResult.Principal.Claims.FirstOrDefault(x => x.Type == JwtClaimTypes.SessionId); + if (sid != null) + { + localClaims.Add(new Claim(JwtClaimTypes.SessionId, sid.Value)); + } + + // if the external provider issued an id_token, we'll keep it for signout + var idToken = externalResult.Properties.GetTokenValue("id_token"); + if (idToken != null) + { + localSignInProps.StoreTokens(new[] { new AuthenticationToken { Name = "id_token", Value = idToken } }); + } + } + } +} \ No newline at end of file diff --git a/Sample/IdentityServer/Quickstart/Account/ExternalProvider.cs b/Sample/IdentityServer/Quickstart/Account/ExternalProvider.cs index 8bc8e71..a183113 100644 --- a/Sample/IdentityServer/Quickstart/Account/ExternalProvider.cs +++ b/Sample/IdentityServer/Quickstart/Account/ExternalProvider.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -namespace IdentityServer4.Quickstart.UI +namespace IdentityServerHost.Quickstart.UI { public class ExternalProvider { diff --git a/Sample/IdentityServer/Quickstart/Account/LoggedOutViewModel.cs b/Sample/IdentityServer/Quickstart/Account/LoggedOutViewModel.cs index da45c51..6368832 100644 --- a/Sample/IdentityServer/Quickstart/Account/LoggedOutViewModel.cs +++ b/Sample/IdentityServer/Quickstart/Account/LoggedOutViewModel.cs @@ -1,8 +1,8 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -namespace IdentityServer4.Quickstart.UI +namespace IdentityServerHost.Quickstart.UI { public class LoggedOutViewModel { diff --git a/Sample/IdentityServer/Quickstart/Account/LoginInputModel.cs b/Sample/IdentityServer/Quickstart/Account/LoginInputModel.cs index 891801e..bcb7853 100644 --- a/Sample/IdentityServer/Quickstart/Account/LoginInputModel.cs +++ b/Sample/IdentityServer/Quickstart/Account/LoginInputModel.cs @@ -1,10 +1,10 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. using System.ComponentModel.DataAnnotations; -namespace IdentityServer4.Quickstart.UI +namespace IdentityServerHost.Quickstart.UI { public class LoginInputModel { @@ -12,7 +12,6 @@ public class LoginInputModel public string Username { get; set; } [Required] public string Password { get; set; } - public string Domain { get; set; } public bool RememberLogin { get; set; } public string ReturnUrl { get; set; } } diff --git a/Sample/IdentityServer/Quickstart/Account/LoginViewModel.cs b/Sample/IdentityServer/Quickstart/Account/LoginViewModel.cs index 2d62f89..9bf6c8f 100644 --- a/Sample/IdentityServer/Quickstart/Account/LoginViewModel.cs +++ b/Sample/IdentityServer/Quickstart/Account/LoginViewModel.cs @@ -1,4 +1,4 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. @@ -6,14 +6,14 @@ using System.Collections.Generic; using System.Linq; -namespace IdentityServer4.Quickstart.UI +namespace IdentityServerHost.Quickstart.UI { public class LoginViewModel : LoginInputModel { - public bool AllowRememberLogin { get; set; } - public bool EnableLocalLogin { get; set; } + public bool AllowRememberLogin { get; set; } = true; + public bool EnableLocalLogin { get; set; } = true; - public IEnumerable ExternalProviders { get; set; } + public IEnumerable ExternalProviders { get; set; } = Enumerable.Empty(); public IEnumerable VisibleExternalProviders => ExternalProviders.Where(x => !String.IsNullOrWhiteSpace(x.DisplayName)); public bool IsExternalLoginOnly => EnableLocalLogin == false && ExternalProviders?.Count() == 1; diff --git a/Sample/IdentityServer/Quickstart/Account/LogoutInputModel.cs b/Sample/IdentityServer/Quickstart/Account/LogoutInputModel.cs index 8962b1f..bb74202 100644 --- a/Sample/IdentityServer/Quickstart/Account/LogoutInputModel.cs +++ b/Sample/IdentityServer/Quickstart/Account/LogoutInputModel.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -namespace IdentityServer4.Quickstart.UI +namespace IdentityServerHost.Quickstart.UI { public class LogoutInputModel { diff --git a/Sample/IdentityServer/Quickstart/Account/LogoutViewModel.cs b/Sample/IdentityServer/Quickstart/Account/LogoutViewModel.cs index a99f03d..236cd6c 100644 --- a/Sample/IdentityServer/Quickstart/Account/LogoutViewModel.cs +++ b/Sample/IdentityServer/Quickstart/Account/LogoutViewModel.cs @@ -1,11 +1,11 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -namespace IdentityServer4.Quickstart.UI +namespace IdentityServerHost.Quickstart.UI { public class LogoutViewModel : LogoutInputModel { - public bool ShowLogoutPrompt { get; set; } + public bool ShowLogoutPrompt { get; set; } = true; } } diff --git a/Sample/IdentityServer/Quickstart/Account/RedirectViewModel.cs b/Sample/IdentityServer/Quickstart/Account/RedirectViewModel.cs new file mode 100644 index 0000000..904ae20 --- /dev/null +++ b/Sample/IdentityServer/Quickstart/Account/RedirectViewModel.cs @@ -0,0 +1,12 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + + +namespace IdentityServerHost.Quickstart.UI +{ + public class RedirectViewModel + { + public string RedirectUrl { get; set; } + } +} \ No newline at end of file diff --git a/Sample/IdentityServer/Quickstart/Consent/ConsentController.cs b/Sample/IdentityServer/Quickstart/Consent/ConsentController.cs index fa51009..6c3aa44 100644 --- a/Sample/IdentityServer/Quickstart/Consent/ConsentController.cs +++ b/Sample/IdentityServer/Quickstart/Consent/ConsentController.cs @@ -1,15 +1,21 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. +using IdentityServer4.Events; +using IdentityServer4.Models; using IdentityServer4.Services; -using IdentityServer4.Stores; +using IdentityServer4.Extensions; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; +using System.Linq; using System.Threading.Tasks; +using IdentityServer4.Validation; +using System.Collections.Generic; +using System; -namespace IdentityServer4.Quickstart.UI +namespace IdentityServerHost.Quickstart.UI { /// /// This controller processes the consent UI @@ -18,15 +24,18 @@ namespace IdentityServer4.Quickstart.UI [Authorize] public class ConsentController : Controller { - private readonly ConsentService _consent; + private readonly IIdentityServerInteractionService _interaction; + private readonly IEventService _events; + private readonly ILogger _logger; public ConsentController( IIdentityServerInteractionService interaction, - IClientStore clientStore, - IResourceStore resourceStore, + IEventService events, ILogger logger) { - _consent = new ConsentService(interaction, clientStore, resourceStore, logger); + _interaction = interaction; + _events = events; + _logger = logger; } /// @@ -37,7 +46,7 @@ public ConsentController( [HttpGet] public async Task Index(string returnUrl) { - var vm = await _consent.BuildViewModelAsync(returnUrl); + var vm = await BuildViewModelAsync(returnUrl); if (vm != null) { return View("Index", vm); @@ -53,16 +62,24 @@ public async Task Index(string returnUrl) [ValidateAntiForgeryToken] public async Task Index(ConsentInputModel model) { - var result = await _consent.ProcessConsent(model); + var result = await ProcessConsent(model); if (result.IsRedirect) { + var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); + if (context?.IsNativeClient() == true) + { + // The client is native, so this change in how to + // return the response is for better UX for the end user. + return this.LoadingPage("Redirect", result.RedirectUri); + } + return Redirect(result.RedirectUri); } if (result.HasValidationError) { - ModelState.AddModelError("", result.ValidationError); + ModelState.AddModelError(string.Empty, result.ValidationError); } if (result.ShowView) @@ -72,5 +89,174 @@ public async Task Index(ConsentInputModel model) return View("Error"); } + + /*****************************************/ + /* helper APIs for the ConsentController */ + /*****************************************/ + private async Task ProcessConsent(ConsentInputModel model) + { + var result = new ProcessConsentResult(); + + // validate return url is still valid + var request = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); + if (request == null) return result; + + ConsentResponse grantedConsent = null; + + // user clicked 'no' - send back the standard 'access_denied' response + if (model?.Button == "no") + { + grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied }; + + // emit event + await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); + } + // user clicked 'yes' - validate the data + else if (model?.Button == "yes") + { + // if the user consented to some scope, build the response model + if (model.ScopesConsented != null && model.ScopesConsented.Any()) + { + var scopes = model.ScopesConsented; + if (ConsentOptions.EnableOfflineAccess == false) + { + scopes = scopes.Where(x => x != IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess); + } + + grantedConsent = new ConsentResponse + { + RememberConsent = model.RememberConsent, + ScopesValuesConsented = scopes.ToArray(), + Description = model.Description + }; + + // emit event + await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); + } + else + { + result.ValidationError = ConsentOptions.MustChooseOneErrorMessage; + } + } + else + { + result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage; + } + + if (grantedConsent != null) + { + // communicate outcome of consent back to identityserver + await _interaction.GrantConsentAsync(request, grantedConsent); + + // indicate that's it ok to redirect back to authorization endpoint + result.RedirectUri = model.ReturnUrl; + result.Client = request.Client; + } + else + { + // we need to redisplay the consent UI + result.ViewModel = await BuildViewModelAsync(model.ReturnUrl, model); + } + + return result; + } + + private async Task BuildViewModelAsync(string returnUrl, ConsentInputModel model = null) + { + var request = await _interaction.GetAuthorizationContextAsync(returnUrl); + if (request != null) + { + return CreateConsentViewModel(model, returnUrl, request); + } + else + { + _logger.LogError("No consent request matching request: {0}", returnUrl); + } + + return null; + } + + private ConsentViewModel CreateConsentViewModel( + ConsentInputModel model, string returnUrl, + AuthorizationRequest request) + { + var vm = new ConsentViewModel + { + RememberConsent = model?.RememberConsent ?? true, + ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty(), + Description = model?.Description, + + ReturnUrl = returnUrl, + + ClientName = request.Client.ClientName ?? request.Client.ClientId, + ClientUrl = request.Client.ClientUri, + ClientLogoUrl = request.Client.LogoUri, + AllowRememberConsent = request.Client.AllowRememberConsent + }; + + vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); + + var apiScopes = new List(); + foreach(var parsedScope in request.ValidatedResources.ParsedScopes) + { + var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName); + if (apiScope != null) + { + var scopeVm = CreateScopeViewModel(parsedScope, apiScope, vm.ScopesConsented.Contains(parsedScope.RawValue) || model == null); + apiScopes.Add(scopeVm); + } + } + if (ConsentOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess) + { + apiScopes.Add(GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess) || model == null)); + } + vm.ApiScopes = apiScopes; + + return vm; + } + + private ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) + { + return new ScopeViewModel + { + Value = identity.Name, + DisplayName = identity.DisplayName ?? identity.Name, + Description = identity.Description, + Emphasize = identity.Emphasize, + Required = identity.Required, + Checked = check || identity.Required + }; + } + + public ScopeViewModel CreateScopeViewModel(ParsedScopeValue parsedScopeValue, ApiScope apiScope, bool check) + { + var displayName = apiScope.DisplayName ?? apiScope.Name; + if (!String.IsNullOrWhiteSpace(parsedScopeValue.ParsedParameter)) + { + displayName += ":" + parsedScopeValue.ParsedParameter; + } + + return new ScopeViewModel + { + Value = parsedScopeValue.RawValue, + DisplayName = displayName, + Description = apiScope.Description, + Emphasize = apiScope.Emphasize, + Required = apiScope.Required, + Checked = check || apiScope.Required + }; + } + + private ScopeViewModel GetOfflineAccessScope(bool check) + { + return new ScopeViewModel + { + Value = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess, + DisplayName = ConsentOptions.OfflineAccessDisplayName, + Description = ConsentOptions.OfflineAccessDescription, + Emphasize = true, + Checked = check + }; + } } } \ No newline at end of file diff --git a/Sample/IdentityServer/Quickstart/Consent/ConsentInputModel.cs b/Sample/IdentityServer/Quickstart/Consent/ConsentInputModel.cs index eb42805..f608fe3 100644 --- a/Sample/IdentityServer/Quickstart/Consent/ConsentInputModel.cs +++ b/Sample/IdentityServer/Quickstart/Consent/ConsentInputModel.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; -namespace IdentityServer4.Quickstart.UI +namespace IdentityServerHost.Quickstart.UI { public class ConsentInputModel { @@ -12,5 +12,6 @@ public class ConsentInputModel public IEnumerable ScopesConsented { get; set; } public bool RememberConsent { get; set; } public string ReturnUrl { get; set; } + public string Description { get; set; } } } \ No newline at end of file diff --git a/Sample/IdentityServer/Quickstart/Consent/ConsentOptions.cs b/Sample/IdentityServer/Quickstart/Consent/ConsentOptions.cs index 0fc990a..998c51d 100644 --- a/Sample/IdentityServer/Quickstart/Consent/ConsentOptions.cs +++ b/Sample/IdentityServer/Quickstart/Consent/ConsentOptions.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -namespace IdentityServer4.Quickstart.UI +namespace IdentityServerHost.Quickstart.UI { public class ConsentOptions { diff --git a/Sample/IdentityServer/Quickstart/Consent/ConsentViewModel.cs b/Sample/IdentityServer/Quickstart/Consent/ConsentViewModel.cs index e1c0b00..af4b9c5 100644 --- a/Sample/IdentityServer/Quickstart/Consent/ConsentViewModel.cs +++ b/Sample/IdentityServer/Quickstart/Consent/ConsentViewModel.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; -namespace IdentityServer4.Quickstart.UI +namespace IdentityServerHost.Quickstart.UI { public class ConsentViewModel : ConsentInputModel { @@ -14,6 +14,6 @@ public class ConsentViewModel : ConsentInputModel public bool AllowRememberConsent { get; set; } public IEnumerable IdentityScopes { get; set; } - public IEnumerable ResourceScopes { get; set; } + public IEnumerable ApiScopes { get; set; } } } diff --git a/Sample/IdentityServer/Quickstart/Consent/ProcessConsentResult.cs b/Sample/IdentityServer/Quickstart/Consent/ProcessConsentResult.cs index c705c14..1d331df 100644 --- a/Sample/IdentityServer/Quickstart/Consent/ProcessConsentResult.cs +++ b/Sample/IdentityServer/Quickstart/Consent/ProcessConsentResult.cs @@ -1,13 +1,16 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -namespace IdentityServer4.Quickstart.UI +using IdentityServer4.Models; + +namespace IdentityServerHost.Quickstart.UI { public class ProcessConsentResult { public bool IsRedirect => RedirectUri != null; public string RedirectUri { get; set; } + public Client Client { get; set; } public bool ShowView => ViewModel != null; public ConsentViewModel ViewModel { get; set; } diff --git a/Sample/IdentityServer/Quickstart/Consent/ScopeViewModel.cs b/Sample/IdentityServer/Quickstart/Consent/ScopeViewModel.cs index 24cc22c..532d1b1 100644 --- a/Sample/IdentityServer/Quickstart/Consent/ScopeViewModel.cs +++ b/Sample/IdentityServer/Quickstart/Consent/ScopeViewModel.cs @@ -2,11 +2,11 @@ // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. -namespace IdentityServer4.Quickstart.UI +namespace IdentityServerHost.Quickstart.UI { public class ScopeViewModel { - public string Name { get; set; } + public string Value { get; set; } public string DisplayName { get; set; } public string Description { get; set; } public bool Emphasize { get; set; } diff --git a/Sample/IdentityServer/Quickstart/Device/DeviceAuthorizationInputModel.cs b/Sample/IdentityServer/Quickstart/Device/DeviceAuthorizationInputModel.cs new file mode 100644 index 0000000..a221181 --- /dev/null +++ b/Sample/IdentityServer/Quickstart/Device/DeviceAuthorizationInputModel.cs @@ -0,0 +1,11 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI +{ + public class DeviceAuthorizationInputModel : ConsentInputModel + { + public string UserCode { get; set; } + } +} \ No newline at end of file diff --git a/Sample/IdentityServer/Quickstart/Device/DeviceAuthorizationViewModel.cs b/Sample/IdentityServer/Quickstart/Device/DeviceAuthorizationViewModel.cs new file mode 100644 index 0000000..3e8857f --- /dev/null +++ b/Sample/IdentityServer/Quickstart/Device/DeviceAuthorizationViewModel.cs @@ -0,0 +1,12 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +namespace IdentityServerHost.Quickstart.UI +{ + public class DeviceAuthorizationViewModel : ConsentViewModel + { + public string UserCode { get; set; } + public bool ConfirmUserCode { get; set; } + } +} \ No newline at end of file diff --git a/Sample/IdentityServer/Quickstart/Device/DeviceController.cs b/Sample/IdentityServer/Quickstart/Device/DeviceController.cs new file mode 100644 index 0000000..d7d07ae --- /dev/null +++ b/Sample/IdentityServer/Quickstart/Device/DeviceController.cs @@ -0,0 +1,232 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using IdentityServer4.Configuration; +using IdentityServer4.Events; +using IdentityServer4.Extensions; +using IdentityServer4.Models; +using IdentityServer4.Services; +using IdentityServer4.Validation; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace IdentityServerHost.Quickstart.UI +{ + [Authorize] + [SecurityHeaders] + public class DeviceController : Controller + { + private readonly IDeviceFlowInteractionService _interaction; + private readonly IEventService _events; + private readonly IOptions _options; + private readonly ILogger _logger; + + public DeviceController( + IDeviceFlowInteractionService interaction, + IEventService eventService, + IOptions options, + ILogger logger) + { + _interaction = interaction; + _events = eventService; + _options = options; + _logger = logger; + } + + [HttpGet] + public async Task Index() + { + string userCodeParamName = _options.Value.UserInteraction.DeviceVerificationUserCodeParameter; + string userCode = Request.Query[userCodeParamName]; + if (string.IsNullOrWhiteSpace(userCode)) return View("UserCodeCapture"); + + var vm = await BuildViewModelAsync(userCode); + if (vm == null) return View("Error"); + + vm.ConfirmUserCode = true; + return View("UserCodeConfirmation", vm); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task UserCodeCapture(string userCode) + { + var vm = await BuildViewModelAsync(userCode); + if (vm == null) return View("Error"); + + return View("UserCodeConfirmation", vm); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Callback(DeviceAuthorizationInputModel model) + { + if (model == null) throw new ArgumentNullException(nameof(model)); + + var result = await ProcessConsent(model); + if (result.HasValidationError) return View("Error"); + + return View("Success"); + } + + private async Task ProcessConsent(DeviceAuthorizationInputModel model) + { + var result = new ProcessConsentResult(); + + var request = await _interaction.GetAuthorizationContextAsync(model.UserCode); + if (request == null) return result; + + ConsentResponse grantedConsent = null; + + // user clicked 'no' - send back the standard 'access_denied' response + if (model.Button == "no") + { + grantedConsent = new ConsentResponse { Error = AuthorizationError.AccessDenied }; + + // emit event + await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues)); + } + // user clicked 'yes' - validate the data + else if (model.Button == "yes") + { + // if the user consented to some scope, build the response model + if (model.ScopesConsented != null && model.ScopesConsented.Any()) + { + var scopes = model.ScopesConsented; + if (ConsentOptions.EnableOfflineAccess == false) + { + scopes = scopes.Where(x => x != IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess); + } + + grantedConsent = new ConsentResponse + { + RememberConsent = model.RememberConsent, + ScopesValuesConsented = scopes.ToArray(), + Description = model.Description + }; + + // emit event + await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.Client.ClientId, request.ValidatedResources.RawScopeValues, grantedConsent.ScopesValuesConsented, grantedConsent.RememberConsent)); + } + else + { + result.ValidationError = ConsentOptions.MustChooseOneErrorMessage; + } + } + else + { + result.ValidationError = ConsentOptions.InvalidSelectionErrorMessage; + } + + if (grantedConsent != null) + { + // communicate outcome of consent back to identityserver + await _interaction.HandleRequestAsync(model.UserCode, grantedConsent); + + // indicate that's it ok to redirect back to authorization endpoint + result.RedirectUri = model.ReturnUrl; + result.Client = request.Client; + } + else + { + // we need to redisplay the consent UI + result.ViewModel = await BuildViewModelAsync(model.UserCode, model); + } + + return result; + } + + private async Task BuildViewModelAsync(string userCode, DeviceAuthorizationInputModel model = null) + { + var request = await _interaction.GetAuthorizationContextAsync(userCode); + if (request != null) + { + return CreateConsentViewModel(userCode, model, request); + } + + return null; + } + + private DeviceAuthorizationViewModel CreateConsentViewModel(string userCode, DeviceAuthorizationInputModel model, DeviceFlowAuthorizationRequest request) + { + var vm = new DeviceAuthorizationViewModel + { + UserCode = userCode, + Description = model?.Description, + + RememberConsent = model?.RememberConsent ?? true, + ScopesConsented = model?.ScopesConsented ?? Enumerable.Empty(), + + ClientName = request.Client.ClientName ?? request.Client.ClientId, + ClientUrl = request.Client.ClientUri, + ClientLogoUrl = request.Client.LogoUri, + AllowRememberConsent = request.Client.AllowRememberConsent + }; + + vm.IdentityScopes = request.ValidatedResources.Resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray(); + + var apiScopes = new List(); + foreach (var parsedScope in request.ValidatedResources.ParsedScopes) + { + var apiScope = request.ValidatedResources.Resources.FindApiScope(parsedScope.ParsedName); + if (apiScope != null) + { + var scopeVm = CreateScopeViewModel(parsedScope, apiScope, vm.ScopesConsented.Contains(parsedScope.RawValue) || model == null); + apiScopes.Add(scopeVm); + } + } + if (ConsentOptions.EnableOfflineAccess && request.ValidatedResources.Resources.OfflineAccess) + { + apiScopes.Add(GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess) || model == null)); + } + vm.ApiScopes = apiScopes; + + return vm; + } + + private ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) + { + return new ScopeViewModel + { + Value = identity.Name, + DisplayName = identity.DisplayName ?? identity.Name, + Description = identity.Description, + Emphasize = identity.Emphasize, + Required = identity.Required, + Checked = check || identity.Required + }; + } + + public ScopeViewModel CreateScopeViewModel(ParsedScopeValue parsedScopeValue, ApiScope apiScope, bool check) + { + return new ScopeViewModel + { + Value = parsedScopeValue.RawValue, + // todo: use the parsed scope value in the display? + DisplayName = apiScope.DisplayName ?? apiScope.Name, + Description = apiScope.Description, + Emphasize = apiScope.Emphasize, + Required = apiScope.Required, + Checked = check || apiScope.Required + }; + } + private ScopeViewModel GetOfflineAccessScope(bool check) + { + return new ScopeViewModel + { + Value = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess, + DisplayName = ConsentOptions.OfflineAccessDisplayName, + Description = ConsentOptions.OfflineAccessDescription, + Emphasize = true, + Checked = check + }; + } + } +} \ No newline at end of file diff --git a/Sample/IdentityServer/Quickstart/Diagnostics/DiagnosticsController.cs b/Sample/IdentityServer/Quickstart/Diagnostics/DiagnosticsController.cs index b92308d..57c2f55 100644 --- a/Sample/IdentityServer/Quickstart/Diagnostics/DiagnosticsController.cs +++ b/Sample/IdentityServer/Quickstart/Diagnostics/DiagnosticsController.cs @@ -1,26 +1,29 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace IdentityServer4.Quickstart.UI +namespace IdentityServerHost.Quickstart.UI { [SecurityHeaders] [Authorize] public class DiagnosticsController : Controller { - public IActionResult Index() + public async Task Index() { var localAddresses = new string[] { "127.0.0.1", "::1", HttpContext.Connection.LocalIpAddress.ToString() }; - if (localAddresses.Contains(HttpContext.Connection.RemoteIpAddress.ToString())) + if (!localAddresses.Contains(HttpContext.Connection.RemoteIpAddress.ToString())) { - return View(); + return NotFound(); } - return NotFound(); + var model = new DiagnosticsViewModel(await HttpContext.AuthenticateAsync()); + return View(model); } } } \ No newline at end of file diff --git a/Sample/IdentityServer/Quickstart/Diagnostics/DiagnosticsViewModel.cs b/Sample/IdentityServer/Quickstart/Diagnostics/DiagnosticsViewModel.cs new file mode 100644 index 0000000..f43c768 --- /dev/null +++ b/Sample/IdentityServer/Quickstart/Diagnostics/DiagnosticsViewModel.cs @@ -0,0 +1,32 @@ +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using IdentityModel; +using Microsoft.AspNetCore.Authentication; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Text; + +namespace IdentityServerHost.Quickstart.UI +{ + public class DiagnosticsViewModel + { + public DiagnosticsViewModel(AuthenticateResult result) + { + AuthenticateResult = result; + + if (result.Properties.Items.ContainsKey("client_list")) + { + var encoded = result.Properties.Items["client_list"]; + var bytes = Base64Url.Decode(encoded); + var value = Encoding.UTF8.GetString(bytes); + + Clients = JsonConvert.DeserializeObject(value); + } + } + + public AuthenticateResult AuthenticateResult { get; } + public IEnumerable Clients { get; } = new List(); + } +} \ No newline at end of file diff --git a/Sample/IdentityServer/Quickstart/Extensions.cs b/Sample/IdentityServer/Quickstart/Extensions.cs new file mode 100644 index 0000000..6c720b7 --- /dev/null +++ b/Sample/IdentityServer/Quickstart/Extensions.cs @@ -0,0 +1,27 @@ +using System; +using IdentityServer4.Models; +using Microsoft.AspNetCore.Mvc; + +namespace IdentityServerHost.Quickstart.UI +{ + public static class Extensions + { + /// + /// Checks if the redirect URI is for a native client. + /// + /// + public static bool IsNativeClient(this AuthorizationRequest context) + { + return !context.RedirectUri.StartsWith("https", StringComparison.Ordinal) + && !context.RedirectUri.StartsWith("http", StringComparison.Ordinal); + } + + public static IActionResult LoadingPage(this Controller controller, string viewName, string redirectUri) + { + controller.HttpContext.Response.StatusCode = 200; + controller.HttpContext.Response.Headers["Location"] = ""; + + return controller.View(viewName, new RedirectViewModel { RedirectUrl = redirectUri }); + } + } +} diff --git a/Sample/IdentityServer/Quickstart/Grants/GrantsController.cs b/Sample/IdentityServer/Quickstart/Grants/GrantsController.cs index a0b42ad..128ce59 100644 --- a/Sample/IdentityServer/Quickstart/Grants/GrantsController.cs +++ b/Sample/IdentityServer/Quickstart/Grants/GrantsController.cs @@ -1,4 +1,4 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. @@ -9,8 +9,10 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; +using IdentityServer4.Events; +using IdentityServer4.Extensions; -namespace IdentityServer4.Quickstart.UI +namespace IdentityServerHost.Quickstart.UI { /// /// This sample controller allows a user to revoke grants given to clients @@ -22,14 +24,17 @@ public class GrantsController : Controller private readonly IIdentityServerInteractionService _interaction; private readonly IClientStore _clients; private readonly IResourceStore _resources; + private readonly IEventService _events; public GrantsController(IIdentityServerInteractionService interaction, IClientStore clients, - IResourceStore resources) + IResourceStore resources, + IEventService events) { _interaction = interaction; _clients = clients; _resources = resources; + _events = events; } /// @@ -49,12 +54,14 @@ public async Task Index() public async Task Revoke(string clientId) { await _interaction.RevokeUserConsentAsync(clientId); + await _events.RaiseAsync(new GrantsRevokedEvent(User.GetSubjectId(), clientId)); + return RedirectToAction("Index"); } private async Task BuildViewModelAsync() { - var grants = await _interaction.GetAllUserConsentsAsync(); + var grants = await _interaction.GetAllUserGrantsAsync(); var list = new List(); foreach(var grant in grants) @@ -70,10 +77,11 @@ private async Task BuildViewModelAsync() ClientName = client.ClientName ?? client.ClientId, ClientLogoUrl = client.LogoUri, ClientUrl = client.ClientUri, + Description = grant.Description, Created = grant.CreationTime, Expires = grant.Expiration, IdentityGrantNames = resources.IdentityResources.Select(x => x.DisplayName ?? x.Name).ToArray(), - ApiGrantNames = resources.ApiResources.Select(x => x.DisplayName ?? x.Name).ToArray() + ApiGrantNames = resources.ApiScopes.Select(x => x.DisplayName ?? x.Name).ToArray() }; list.Add(item); diff --git a/Sample/IdentityServer/Quickstart/Grants/GrantsViewModel.cs b/Sample/IdentityServer/Quickstart/Grants/GrantsViewModel.cs index 4fa818e..2114dee 100644 --- a/Sample/IdentityServer/Quickstart/Grants/GrantsViewModel.cs +++ b/Sample/IdentityServer/Quickstart/Grants/GrantsViewModel.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; -namespace IdentityServer4.Quickstart.UI +namespace IdentityServerHost.Quickstart.UI { public class GrantsViewModel { @@ -18,6 +18,7 @@ public class GrantViewModel public string ClientName { get; set; } public string ClientUrl { get; set; } public string ClientLogoUrl { get; set; } + public string Description { get; set; } public DateTime Created { get; set; } public DateTime? Expires { get; set; } public IEnumerable IdentityGrantNames { get; set; } diff --git a/Sample/IdentityServer/Quickstart/Home/ErrorViewModel.cs b/Sample/IdentityServer/Quickstart/Home/ErrorViewModel.cs index c1a27b8..a29a21a 100644 --- a/Sample/IdentityServer/Quickstart/Home/ErrorViewModel.cs +++ b/Sample/IdentityServer/Quickstart/Home/ErrorViewModel.cs @@ -4,10 +4,19 @@ using IdentityServer4.Models; -namespace IdentityServer4.Quickstart.UI +namespace IdentityServerHost.Quickstart.UI { public class ErrorViewModel { + public ErrorViewModel() + { + } + + public ErrorViewModel(string error) + { + Error = new ErrorMessage { Error = error }; + } + public ErrorMessage Error { get; set; } } } \ No newline at end of file diff --git a/Sample/IdentityServer/Quickstart/Home/HomeController.cs b/Sample/IdentityServer/Quickstart/Home/HomeController.cs index 80beb21..9cf0678 100644 --- a/Sample/IdentityServer/Quickstart/Home/HomeController.cs +++ b/Sample/IdentityServer/Quickstart/Home/HomeController.cs @@ -1,26 +1,42 @@ -// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. // Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. using IdentityServer4.Services; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; using System.Threading.Tasks; -namespace IdentityServer4.Quickstart.UI +namespace IdentityServerHost.Quickstart.UI { [SecurityHeaders] + [AllowAnonymous] public class HomeController : Controller { private readonly IIdentityServerInteractionService _interaction; + private readonly IWebHostEnvironment _environment; + private readonly ILogger _logger; - public HomeController(IIdentityServerInteractionService interaction) + public HomeController(IIdentityServerInteractionService interaction, IWebHostEnvironment environment, ILogger logger) { _interaction = interaction; + _environment = environment; + _logger = logger; } public IActionResult Index() { - return View(); + if (_environment.IsDevelopment()) + { + // only show in development + return View(); + } + + _logger.LogInformation("Homepage is disabled in production. Returning 404."); + return NotFound(); } /// @@ -35,6 +51,12 @@ public async Task Error(string errorId) if (message != null) { vm.Error = message; + + if (!_environment.IsDevelopment()) + { + // only show in development + message.ErrorDescription = null; + } } return View("Error", vm); diff --git a/Sample/IdentityServer/Quickstart/SecurityHeadersAttribute.cs b/Sample/IdentityServer/Quickstart/SecurityHeadersAttribute.cs index dcdf6f5..93ca67f 100644 --- a/Sample/IdentityServer/Quickstart/SecurityHeadersAttribute.cs +++ b/Sample/IdentityServer/Quickstart/SecurityHeadersAttribute.cs @@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; -namespace IdentityServer4.Quickstart.UI +namespace IdentityServerHost.Quickstart.UI { public class SecurityHeadersAttribute : ActionFilterAttribute { diff --git a/Sample/IdentityServer/QuickstartIdentityServer412.csproj b/Sample/IdentityServer/QuickstartIdentityServer412.csproj new file mode 100644 index 0000000..7295825 --- /dev/null +++ b/Sample/IdentityServer/QuickstartIdentityServer412.csproj @@ -0,0 +1,32 @@ + + + + net5.0 + InProcess + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Sample/IdentityServer/Startup.cs b/Sample/IdentityServer/Startup.cs index 3858bdb..e7b1863 100644 --- a/Sample/IdentityServer/Startup.cs +++ b/Sample/IdentityServer/Startup.cs @@ -1,51 +1,273 @@ -using IdentityServer.LdapExtension.Extensions; +// Copyright (c) Brock Allen & Dominick Baier. All rights reserved. +// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information. + + +using System; +using IdentityServerHost.Configuration; +using IdentityModel; +using IdentityServer4; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.Tokens; +using Serilog; +using System.Linq; +using System.Security.Claims; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; +using IdentityServerHost.Extensions; +using Microsoft.AspNetCore.Authentication.Certificate; +using Microsoft.AspNetCore.HttpOverrides; +using IdentityServer.LdapExtension.Extensions; using IdentityServer.LdapExtension.UserModel; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; +using QuickstartIdentityServer412; -namespace QuickstartIdentityServer -{ - public class Startup - { - public Startup(IWebHostEnvironment env, IConfiguration configuration) - { - Env = env; - Configuration = configuration; - } - - private IWebHostEnvironment Env { get; } - private IConfiguration Configuration { get; } - - public void ConfigureServices(IServiceCollection services) - { - services.AddMvc(); - - // configure identity server with in-memory stores, keys, clients and scopes - services.AddIdentityServer() - .AddDeveloperSigningCredential() - ////.AddSigningCredential(...) // Strongly recommended, if you want something more secure than developer signing (Read The Manual since it's highly recommended) - .AddInMemoryIdentityResources(InMemoryInitConfig.GetIdentityResources()) - .AddInMemoryApiResources(InMemoryInitConfig.GetApiResources()) - .AddInMemoryClients(InMemoryInitConfig.GetClients()) - .AddLdapUsers(Configuration.GetSection("IdentityServerLdap"), UserStore.InMemory); - } - - public void Configure(IApplicationBuilder app) - { - if (Env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseIdentityServer(); - app.UseStaticFiles(); - app.UseRouting(); - app.UseAuthorization(); - - app.UseEndpoints(c => c.MapDefaultControllerRoute()); - } - } +namespace IdentityServerHost +{ + public class Startup + { + private readonly IConfiguration _config; + + public Startup(IConfiguration config) + { + _config = config; + + IdentityModelEventSource.ShowPII = true; + } + + public void ConfigureServices(IServiceCollection services) + { + services.AddControllersWithViews(); + + // cookie policy to deal with temporary browser incompatibilities + services.AddSameSiteCookiePolicy(); + + var builder = services.AddIdentityServer(options => + { + options.Events.RaiseSuccessEvents = true; + options.Events.RaiseFailureEvents = true; + options.Events.RaiseErrorEvents = true; + options.Events.RaiseInformationEvents = true; + + options.EmitScopesAsSpaceDelimitedStringInJwt = true; + + options.MutualTls.Enabled = true; + options.MutualTls.DomainName = "mtls"; + //options.MutualTls.AlwaysEmitConfirmationClaim = true; + }) + //.AddInMemoryClients(Clients.Get()) // [Original code] + //.AddInMemoryIdentityResources(Resources.IdentityResources) [Original code] + //.AddInMemoryApiScopes(Resources.ApiScopes) // [Original code] + .AddInMemoryClients(Config.GetClients()) // [LDAP API Example] + .AddInMemoryIdentityResources(Config.IdentityResources()) // [LDAP API Example] + .AddInMemoryApiScopes(Config.GetApiScope()) // [LDAP API Example] + .AddInMemoryApiResources(Resources.ApiResources) + .AddSigningCredential() + .AddExtensionGrantValidator() + .AddExtensionGrantValidator() + .AddJwtBearerClientAuthentication() + .AddAppAuthRedirectUriValidator() + // [START of Usage of ldap] + .AddLdapUsers(_config.GetSection("IdentityServerLdap"), UserStore.InMemory) + .AddProfileService() // Upgraded for LDAP (see class for details). + // [END of usage of ldap] + .AddCustomTokenRequestValidator() + .AddScopeParser() + .AddMutualTlsSecretValidators(); + + // use this for persisted grants store + // var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; + // const string connectionString = "DataSource=identityserver.db"; + // builder.AddOperationalStore(options => + // { + // options.ConfigureDbContext = b => b.UseSqlite(connectionString, + // sql => sql.MigrationsAssembly(migrationsAssembly)); + // }); + + + services.AddExternalIdentityProviders(); + + services.AddAuthentication() + .AddCertificate(options => + { + options.AllowedCertificateTypes = CertificateTypes.All; + options.RevocationMode = X509RevocationMode.NoCheck; + }); + + services.AddCertificateForwardingForNginx(); + + services.AddLocalApiAuthentication(principal => + { + principal.Identities.First().AddClaim(new Claim("additional_claim", "additional_value")); + + return Task.FromResult(principal); + }); + } + + public void Configure(IApplicationBuilder app) + { + // use this for persisted grants store + // app.InitializePersistedGrantsStore(); + + app.UseForwardedHeaders(new ForwardedHeadersOptions + { + ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto + }); + + app.UseCertificateForwarding(); + app.UseCookiePolicy(); + + app.UseSerilogRequestLogging(); + + app.UseDeveloperExceptionPage(); + app.UseStaticFiles(); + + app.UseRouting(); + app.UseIdentityServer(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapDefaultControllerRoute(); + }); + } + } + + public static class BuilderExtensions + { + public static IIdentityServerBuilder AddSigningCredential(this IIdentityServerBuilder builder) + { + // create random RS256 key + //builder.AddDeveloperSigningCredential(); + + // use an RSA-based certificate with RS256 + var rsaCert = new X509Certificate2("./keys/identityserver.test.rsa.p12", "changeit"); + builder.AddSigningCredential(rsaCert, "RS256"); + + // ...and PS256 + builder.AddSigningCredential(rsaCert, "PS256"); + + // or manually extract ECDSA key from certificate (directly using the certificate is not support by Microsoft right now) + var ecCert = new X509Certificate2("./keys/identityserver.test.ecdsa.p12", "changeit"); + var key = new ECDsaSecurityKey(ecCert.GetECDsaPrivateKey()) + { + KeyId = CryptoRandom.CreateUniqueId(16, CryptoRandom.OutputFormat.Hex) + }; + + return builder.AddSigningCredential( + key, + IdentityServerConstants.ECDsaSigningAlgorithm.ES256); + } + + // use this for persisted grants store + // public static void InitializePersistedGrantsStore(this IApplicationBuilder app) + // { + // using (var serviceScope = app.ApplicationServices.GetService().CreateScope()) + // { + // serviceScope.ServiceProvider.GetRequiredService().Database.Migrate(); + // } + // } + } + + public static class ServiceExtensions + { + public static IServiceCollection AddExternalIdentityProviders(this IServiceCollection services) + { + // configures the OpenIdConnect handlers to persist the state parameter into the server-side IDistributedCache. + services.AddOidcStateDataFormatterCache("aad", "demoidsrv"); + + services.AddAuthentication() + .AddOpenIdConnect("Google", "Google", options => + { + options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; + options.ForwardSignOut = IdentityServerConstants.DefaultCookieAuthenticationScheme; + + options.Authority = "https://accounts.google.com/"; + options.ClientId = "708996912208-9m4dkjb5hscn7cjrn5u0r4tbgkbj1fko.apps.googleusercontent.com"; + + options.CallbackPath = "/signin-google"; + options.Scope.Add("email"); + }) + .AddOpenIdConnect("demoidsrv", "IdentityServer", options => + { + options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; + options.SignOutScheme = IdentityServerConstants.SignoutScheme; + + options.Authority = "https://demo.identityserver.io/"; + options.ClientId = "login"; + options.ResponseType = "id_token"; + options.SaveTokens = true; + options.CallbackPath = "/signin-idsrv"; + options.SignedOutCallbackPath = "/signout-callback-idsrv"; + options.RemoteSignOutPath = "/signout-idsrv"; + + options.TokenValidationParameters = new TokenValidationParameters + { + NameClaimType = "name", + RoleClaimType = "role" + }; + }) + .AddOpenIdConnect("aad", "Azure AD", options => + { + options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; + options.SignOutScheme = IdentityServerConstants.SignoutScheme; + + options.Authority = "https://login.windows.net/4ca9cb4c-5e5f-4be9-b700-c532992a3705"; + options.ClientId = "96e3c53e-01cb-4244-b658-a42164cb67a9"; + options.ResponseType = "id_token"; + options.CallbackPath = "/signin-aad"; + options.SignedOutCallbackPath = "/signout-callback-aad"; + options.RemoteSignOutPath = "/signout-aad"; + options.TokenValidationParameters = new TokenValidationParameters + { + NameClaimType = "name", + RoleClaimType = "role" + }; + }) + .AddOpenIdConnect("adfs", "ADFS", options => + { + options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme; + options.SignOutScheme = IdentityServerConstants.SignoutScheme; + + options.Authority = "https://adfs.leastprivilege.vm/adfs"; + options.ClientId = "c0ea8d99-f1e7-43b0-a100-7dee3f2e5c3c"; + options.ResponseType = "id_token"; + + options.CallbackPath = "/signin-adfs"; + options.SignedOutCallbackPath = "/signout-callback-adfs"; + options.RemoteSignOutPath = "/signout-adfs"; + options.TokenValidationParameters = new TokenValidationParameters + { + NameClaimType = "name", + RoleClaimType = "role" + }; + }); + + return services; + } + + public static void AddCertificateForwardingForNginx(this IServiceCollection services) + { + services.AddCertificateForwarding(options => + { + options.CertificateHeader = "X-SSL-CERT"; + + options.HeaderConverter = (headerValue) => + { + X509Certificate2 clientCertificate = null; + + if(!string.IsNullOrWhiteSpace(headerValue)) + { + byte[] bytes = Encoding.UTF8.GetBytes(Uri.UnescapeDataString(headerValue)); + clientCertificate = new X509Certificate2(bytes); + } + + return clientCertificate; + }; + }); + } + } } \ No newline at end of file diff --git a/Sample/IdentityServer/Views/Account/AccessDenied.cshtml b/Sample/IdentityServer/Views/Account/AccessDenied.cshtml new file mode 100644 index 0000000..0745189 --- /dev/null +++ b/Sample/IdentityServer/Views/Account/AccessDenied.cshtml @@ -0,0 +1,7 @@ + +
+
+

Access Denied

+

You do not have access to that resource.

+
+
\ No newline at end of file diff --git a/Sample/IdentityServer/Views/Account/LoggedOut.cshtml b/Sample/IdentityServer/Views/Account/LoggedOut.cshtml index 99599c0..dc9fbf7 100644 --- a/Sample/IdentityServer/Views/Account/LoggedOut.cshtml +++ b/Sample/IdentityServer/Views/Account/LoggedOut.cshtml @@ -5,7 +5,7 @@ ViewData["signed-out"] = true; } -