Removes IdentityServer4 usage
parent
dc37c9a9f0
commit
838de379fd
@ -1,67 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using IdentityServer4;
|
|
||||||
using IdentityServer4.Models;
|
|
||||||
using IdentityServer4.Test;
|
|
||||||
|
|
||||||
namespace nuget_host
|
|
||||||
{
|
|
||||||
public static class Config
|
|
||||||
{
|
|
||||||
public static IEnumerable<IdentityResource> IdentityResources =>
|
|
||||||
new List<IdentityResource>
|
|
||||||
{
|
|
||||||
new IdentityResources.OpenId(),
|
|
||||||
new IdentityResources.Profile(),
|
|
||||||
};
|
|
||||||
|
|
||||||
public static IEnumerable<ApiResource> ApiResources =>
|
|
||||||
new List<ApiResource>
|
|
||||||
{
|
|
||||||
new ApiResource(IdentityServerConstants.LocalApi.ScopeName),
|
|
||||||
new ApiResource(scope_packages)
|
|
||||||
};
|
|
||||||
|
|
||||||
public const string scope_packages = "packages";
|
|
||||||
|
|
||||||
public static IEnumerable<Client> Clients =>
|
|
||||||
new List<Client>
|
|
||||||
{
|
|
||||||
// machine to machine client
|
|
||||||
new Client
|
|
||||||
{
|
|
||||||
ClientId = "client",
|
|
||||||
ClientSecrets = { new Secret("secret".Sha256()) },
|
|
||||||
|
|
||||||
AllowedGrantTypes = GrantTypes.ClientCredentials,
|
|
||||||
// scopes that client has access to
|
|
||||||
AllowedScopes = { scope_packages,
|
|
||||||
IdentityServerConstants.LocalApi.ScopeName }
|
|
||||||
},
|
|
||||||
|
|
||||||
// interactive ASP.NET Core MVC client
|
|
||||||
new Client
|
|
||||||
{
|
|
||||||
ClientId = "mvc",
|
|
||||||
ClientSecrets = { new Secret("secret".Sha256()) },
|
|
||||||
|
|
||||||
AllowedGrantTypes = GrantTypes.Code,
|
|
||||||
|
|
||||||
// where to redirect to after login
|
|
||||||
RedirectUris = { "https://localhost:5002/signin-oidc" },
|
|
||||||
|
|
||||||
// where to redirect to after logout
|
|
||||||
PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" },
|
|
||||||
|
|
||||||
AllowedScopes = new List<string>
|
|
||||||
{
|
|
||||||
IdentityServerConstants.StandardScopes.OpenId,
|
|
||||||
IdentityServerConstants.StandardScopes.Profile,
|
|
||||||
IdentityServerConstants.LocalApi.ScopeName,
|
|
||||||
scope_packages
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public static List<TestUser> TestUsers { get; internal set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,260 +0,0 @@
|
|||||||
// 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.Extensions;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System;
|
|
||||||
using nuget_host.Models;
|
|
||||||
|
|
||||||
namespace nuget_host.Controllers
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This controller processes the consent UI
|
|
||||||
/// </summary>
|
|
||||||
[SecurityHeaders]
|
|
||||||
[Authorize]
|
|
||||||
public class ConsentController : Controller
|
|
||||||
{
|
|
||||||
private readonly IIdentityServerInteractionService _interaction;
|
|
||||||
private readonly IEventService _events;
|
|
||||||
private readonly ILogger<ConsentController> _logger;
|
|
||||||
|
|
||||||
public ConsentController(
|
|
||||||
IIdentityServerInteractionService interaction,
|
|
||||||
IEventService events,
|
|
||||||
ILogger<ConsentController> logger)
|
|
||||||
{
|
|
||||||
_interaction = interaction;
|
|
||||||
_events = events;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Shows the consent screen
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="returnUrl"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[HttpGet]
|
|
||||||
public async Task<IActionResult> Index(string returnUrl)
|
|
||||||
{
|
|
||||||
var vm = await BuildViewModelAsync(returnUrl);
|
|
||||||
if (vm != null)
|
|
||||||
{
|
|
||||||
return View("Index", vm);
|
|
||||||
}
|
|
||||||
|
|
||||||
return View("Error");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handles the consent screen postback
|
|
||||||
/// </summary>
|
|
||||||
[HttpPost]
|
|
||||||
[ValidateAntiForgeryToken]
|
|
||||||
public async Task<IActionResult> Index(ConsentInputModel 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(string.Empty, result.ValidationError);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.ShowView)
|
|
||||||
{
|
|
||||||
return View("Index", result.ViewModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
return View("Error");
|
|
||||||
}
|
|
||||||
|
|
||||||
/*****************************************/
|
|
||||||
/* helper APIs for the ConsentController */
|
|
||||||
/*****************************************/
|
|
||||||
private async Task<ProcessConsentResult> 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 = ConsentResponse.Denied;
|
|
||||||
|
|
||||||
// emit event
|
|
||||||
await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.ClientId, request.ScopesRequested));
|
|
||||||
}
|
|
||||||
// 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,
|
|
||||||
ScopesConsented = scopes.ToArray()
|
|
||||||
};
|
|
||||||
|
|
||||||
// emit event
|
|
||||||
await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.ClientId, request.ScopesRequested, grantedConsent.ScopesConsented, 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 = model.Client;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// we need to redisplay the consent UI
|
|
||||||
result.ViewModel = await BuildViewModelAsync(model.ReturnUrl, model);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<ConsentViewModel> 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<string>(),
|
|
||||||
Description = model?.Description,
|
|
||||||
|
|
||||||
ReturnUrl = returnUrl,
|
|
||||||
ClientName = model.ClientName,
|
|
||||||
ClientUrl = model.ClientUri,
|
|
||||||
ClientLogoUrl = model.LogoUri,
|
|
||||||
AllowRememberConsent = model.AllowRememberConsent
|
|
||||||
};
|
|
||||||
|
|
||||||
vm.IdentityScopes = model.ValidatedResources.Resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray();
|
|
||||||
|
|
||||||
var apiScopes = new List<ScopeViewModel>();
|
|
||||||
foreach(var parsedScope in model.ValidatedResources.ParsedScopes.Scopes)
|
|
||||||
{
|
|
||||||
var apiScope = model.ValidatedResources.Resources.FindApiScope(parsedScope);
|
|
||||||
if (apiScope != null)
|
|
||||||
{
|
|
||||||
var scopeVm = CreateScopeViewModel(model.ValidatedResources.ParsedScopes, apiScope, vm.ScopesConsented.Contains(model.ValidatedResources.ParsedScopes.RawValue) || model == null);
|
|
||||||
apiScopes.Add(scopeVm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ConsentOptions.EnableOfflineAccess && model.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(ParsedScopes parsedScope, Scope apiScope, bool check)
|
|
||||||
{
|
|
||||||
var displayName = apiScope.DisplayName;
|
|
||||||
if (!String.IsNullOrWhiteSpace(parsedScope.RawValue))
|
|
||||||
{
|
|
||||||
displayName += ":" + parsedScope.RawValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ScopeViewModel
|
|
||||||
{
|
|
||||||
Value = parsedScope.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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
// 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 nuget_host.Models
|
|
||||||
{
|
|
||||||
[SecurityHeaders]
|
|
||||||
[Authorize]
|
|
||||||
public class DiagnosticsController : Controller
|
|
||||||
{
|
|
||||||
public async Task<IActionResult> Index()
|
|
||||||
{
|
|
||||||
var localAddresses = new string[] { "127.0.0.1", "::1", HttpContext.Connection.LocalIpAddress.ToString() };
|
|
||||||
if (!localAddresses.Contains(HttpContext.Connection.RemoteIpAddress.ToString()))
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
var model = new DiagnosticsViewModel(await HttpContext.AuthenticateAsync());
|
|
||||||
return View(model);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,198 +0,0 @@
|
|||||||
using IdentityModel;
|
|
||||||
using IdentityServer4;
|
|
||||||
using IdentityServer4.Events;
|
|
||||||
using IdentityServer4.Services;
|
|
||||||
using IdentityServer4.Stores;
|
|
||||||
using IdentityServer4.Test;
|
|
||||||
using Microsoft.AspNetCore.Authentication;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using nuget_host.Models;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Claims;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace nuget_host.Controllers
|
|
||||||
{
|
|
||||||
[SecurityHeaders]
|
|
||||||
[AllowAnonymous]
|
|
||||||
public class ExternalController : Controller
|
|
||||||
{
|
|
||||||
private readonly TestUserStore _users;
|
|
||||||
private readonly IIdentityServerInteractionService _interaction;
|
|
||||||
private readonly IClientStore _clientStore;
|
|
||||||
private readonly ILogger<ExternalController> _logger;
|
|
||||||
private readonly IEventService _events;
|
|
||||||
|
|
||||||
public ExternalController(
|
|
||||||
IIdentityServerInteractionService interaction,
|
|
||||||
IClientStore clientStore,
|
|
||||||
IEventService events,
|
|
||||||
ILogger<ExternalController> logger,
|
|
||||||
TestUserStore users = null)
|
|
||||||
{
|
|
||||||
// if the TestUserStore is not in DI, then we'll just use the global users collection
|
|
||||||
// this is where you would plug in your own custom identity management library (e.g. ASP.NET Identity)
|
|
||||||
_users = users ?? new TestUserStore(TestUsers.Users);
|
|
||||||
|
|
||||||
_interaction = interaction;
|
|
||||||
_clientStore = clientStore;
|
|
||||||
_logger = logger;
|
|
||||||
_events = events;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// initiate roundtrip to external authentication provider
|
|
||||||
/// </summary>
|
|
||||||
[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);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Post processing of external authentication
|
|
||||||
/// </summary>
|
|
||||||
[HttpGet]
|
|
||||||
public async Task<IActionResult> 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<Claim>();
|
|
||||||
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?.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 (TestUser user, string provider, string providerUserId, IEnumerable<Claim> 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 TestUser AutoProvisionUser(string provider, string providerUserId, IEnumerable<Claim> 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<Claim> 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 } });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,97 +0,0 @@
|
|||||||
// 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 IdentityServer4.Stores;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using IdentityServer4.Events;
|
|
||||||
using IdentityServer4.Extensions;
|
|
||||||
|
|
||||||
namespace nuget_host.Models
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// This sample controller allows a user to revoke grants given to clients
|
|
||||||
/// </summary>
|
|
||||||
[SecurityHeaders]
|
|
||||||
[Authorize]
|
|
||||||
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,
|
|
||||||
IEventService events)
|
|
||||||
{
|
|
||||||
_interaction = interaction;
|
|
||||||
_clients = clients;
|
|
||||||
_resources = resources;
|
|
||||||
_events = events;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Show list of grants
|
|
||||||
/// </summary>
|
|
||||||
[HttpGet]
|
|
||||||
public async Task<IActionResult> Index()
|
|
||||||
{
|
|
||||||
return View("Index", await BuildViewModelAsync());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Handle postback to revoke a client
|
|
||||||
/// </summary>
|
|
||||||
[HttpPost]
|
|
||||||
[ValidateAntiForgeryToken]
|
|
||||||
public async Task<IActionResult> Revoke(string clientId)
|
|
||||||
{
|
|
||||||
await _interaction.RevokeUserConsentAsync(clientId);
|
|
||||||
await _events.RaiseAsync(new GrantsRevokedEvent(User.GetSubjectId(), clientId));
|
|
||||||
|
|
||||||
return RedirectToAction("Index");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<GrantsViewModel> BuildViewModelAsync()
|
|
||||||
{
|
|
||||||
var grants = await _interaction.GetAllUserConsentsAsync();
|
|
||||||
|
|
||||||
var list = new List<GrantViewModel>();
|
|
||||||
foreach(var grant in grants)
|
|
||||||
{
|
|
||||||
var client = await _clients.FindClientByIdAsync(grant.ClientId);
|
|
||||||
if (client != null)
|
|
||||||
{
|
|
||||||
var resources = await _resources.FindResourcesByScopeAsync(grant.Scopes);
|
|
||||||
|
|
||||||
var item = new GrantViewModel()
|
|
||||||
{
|
|
||||||
ClientId = client.ClientId,
|
|
||||||
ClientName = client.ClientName ?? client.ClientId,
|
|
||||||
ClientLogoUrl = client.LogoUri,
|
|
||||||
ClientUrl = client.ClientUri,
|
|
||||||
Description = client.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()
|
|
||||||
};
|
|
||||||
|
|
||||||
list.Add(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return new GrantsViewModel
|
|
||||||
{
|
|
||||||
Grants = list
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
// 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.Models;
|
|
||||||
|
|
||||||
namespace nuget_host.Models
|
|
||||||
{
|
|
||||||
public class ParsedScopes
|
|
||||||
{
|
|
||||||
public ParsedScopes(ParsedSecret secret)
|
|
||||||
{
|
|
||||||
RawValue = secret.Properties.ContainsKey(KEY_SCOPES) ? null : secret.Properties[KEY_SCOPES];
|
|
||||||
Emphasize = secret.Properties.ContainsKey(KEY_OL);
|
|
||||||
if (secret.Properties.ContainsKey(KEY_SCOPES)) Scopes = secret.Properties[KEY_SCOPES].Split(',');
|
|
||||||
}
|
|
||||||
|
|
||||||
public const string KEY_SCOPES = "scopes";
|
|
||||||
public const string KEY_OL = "ol";
|
|
||||||
public string RawValue {
|
|
||||||
get ;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public string[] Scopes { get ; protected set; }
|
|
||||||
public bool Emphasize { get; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
// 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;
|
|
||||||
|
|
||||||
namespace nuget_host.Models
|
|
||||||
{
|
|
||||||
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; }
|
|
||||||
|
|
||||||
public bool HasValidationError => ValidationError != null;
|
|
||||||
public string ValidationError { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
// 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 NuGet.Packaging;
|
|
||||||
|
|
||||||
namespace nuget_host.OAuth
|
|
||||||
{
|
|
||||||
internal class NHAuthorizationRequest : AuthorizationRequest
|
|
||||||
{
|
|
||||||
internal NHAuthorizationRequest(ValidatedAuthorizeRequest request) : base()
|
|
||||||
{
|
|
||||||
ClientId = request.ClientId;
|
|
||||||
RedirectUri = request.RedirectUri;
|
|
||||||
DisplayMode = request.DisplayMode;
|
|
||||||
UiLocales = request.UiLocales;
|
|
||||||
IdP = request.GetIdP();
|
|
||||||
Tenant = request.GetTenant();
|
|
||||||
LoginHint = request.LoginHint;
|
|
||||||
PromptMode = request.PromptMode;
|
|
||||||
AcrValues = request.GetAcrValues();
|
|
||||||
ScopesRequested = request.RequestedScopes;
|
|
||||||
Parameters.Add(request.Raw);
|
|
||||||
RequestObjectValues.AddRange(request.RequestObjectValues);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
// 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 nuget_host.Models
|
|
||||||
{
|
|
||||||
public class ValidatedResources
|
|
||||||
{
|
|
||||||
public Resources Resources { get; set; }
|
|
||||||
public ParsedScopes ParsedScopes { get; internal set; }
|
|
||||||
public bool OfflineAccess { get; internal set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
// 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 nuget_host.Models
|
|
||||||
{
|
|
||||||
|
|
||||||
public class DeviceAuthorizationInputModel : ConsentInputModel
|
|
||||||
{
|
|
||||||
public string UserCode { get; set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
// 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 nuget_host.Models
|
|
||||||
{
|
|
||||||
public class DeviceAuthorizationViewModel : ConsentViewModel
|
|
||||||
{
|
|
||||||
public string UserCode { get; set; }
|
|
||||||
public bool ConfirmUserCode { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,255 +0,0 @@
|
|||||||
// 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 IdentityModel.Client;
|
|
||||||
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;
|
|
||||||
using nuget_host.OAuth;
|
|
||||||
|
|
||||||
namespace nuget_host.Models
|
|
||||||
{
|
|
||||||
[Authorize]
|
|
||||||
[SecurityHeaders]
|
|
||||||
public class DeviceController : Controller
|
|
||||||
{
|
|
||||||
private readonly IDeviceFlowInteractionService _interaction;
|
|
||||||
private readonly IEventService _events;
|
|
||||||
private readonly IOptions<IdentityServerOptions> _options;
|
|
||||||
private readonly ILogger<DeviceController> _logger;
|
|
||||||
|
|
||||||
public DeviceController(
|
|
||||||
IDeviceFlowInteractionService interaction,
|
|
||||||
IEventService eventService,
|
|
||||||
IOptions<IdentityServerOptions> options,
|
|
||||||
ILogger<DeviceController> logger)
|
|
||||||
{
|
|
||||||
_interaction = interaction;
|
|
||||||
_events = eventService;
|
|
||||||
_options = options;
|
|
||||||
_logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpGet]
|
|
||||||
public async Task<IActionResult> 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<IActionResult> UserCodeCapture(string userCode)
|
|
||||||
{
|
|
||||||
var vm = await BuildViewModelAsync(userCode);
|
|
||||||
if (vm == null) return View("Error");
|
|
||||||
|
|
||||||
return View("UserCodeConfirmation", vm);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost]
|
|
||||||
[ValidateAntiForgeryToken]
|
|
||||||
public async Task<IActionResult> 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<ProcessConsentResult> 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 = ConsentResponse.Denied;
|
|
||||||
|
|
||||||
// emit event
|
|
||||||
await _events.RaiseAsync(new ConsentDeniedEvent(User.GetSubjectId(), request.ClientId, request.ScopesRequested));
|
|
||||||
}
|
|
||||||
// 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,
|
|
||||||
ScopesConsented = scopes.ToArray()
|
|
||||||
};
|
|
||||||
|
|
||||||
// emit event
|
|
||||||
await _events.RaiseAsync(new ConsentGrantedEvent(User.GetSubjectId(), request.ClientId, request.ScopesRequested, grantedConsent.ScopesConsented, 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 = model.Client;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// we need to redisplay the consent UI
|
|
||||||
result.ViewModel = await BuildViewModelAsync(model.UserCode, model);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<DeviceAuthorizationViewModel> 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<string>(),
|
|
||||||
|
|
||||||
ClientName = model?.ClientName,
|
|
||||||
ClientUrl = model?.ClientUri,
|
|
||||||
ClientLogoUrl = model?.LogoUri,
|
|
||||||
AllowRememberConsent = model != null && model.AllowRememberConsent
|
|
||||||
};
|
|
||||||
|
|
||||||
vm.IdentityScopes = model.ValidatedResources.Resources.IdentityResources.Select(x => CreateScopeViewModel(x, vm.ScopesConsented.Contains(x.Name) || model == null)).ToArray();
|
|
||||||
|
|
||||||
var apiScopes = new List<ScopeViewModel>();
|
|
||||||
foreach (var parsedScope in model.ValidatedResources.ParsedScopes.Scopes)
|
|
||||||
{
|
|
||||||
var apiScope = model.ValidatedResources.Resources.FindApiScope(parsedScope);
|
|
||||||
if (apiScope != null)
|
|
||||||
{
|
|
||||||
var vreq = CreateValidatedRequest(request, apiScope);
|
|
||||||
|
|
||||||
var scopeVm = CreateScopeViewModel(apiScope,vreq);
|
|
||||||
apiScopes.Add(scopeVm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ConsentOptions.EnableOfflineAccess && model.ValidatedResources.Resources.OfflineAccess)
|
|
||||||
{
|
|
||||||
apiScopes.Add(GetOfflineAccessScope(vm.ScopesConsented.Contains(IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess) || model == null));
|
|
||||||
}
|
|
||||||
vm.ApiScopes = apiScopes;
|
|
||||||
|
|
||||||
return vm;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ValidatedAuthorizeRequest CreateValidatedRequest(DeviceFlowAuthorizationRequest request, Scope apiScope)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ScopeViewModel CreateScopeViewModel(Scope scope, ValidatedAuthorizeRequest req)
|
|
||||||
{
|
|
||||||
return new ScopeViewModel
|
|
||||||
{
|
|
||||||
Value = scope.Name,
|
|
||||||
DisplayName = scope.DisplayName ?? scope.Name,
|
|
||||||
Description = scope.Description,
|
|
||||||
Emphasize = scope.Emphasize,
|
|
||||||
Required = scope.Required,
|
|
||||||
Checked = req.Client != null
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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(ParsedScopes parsedScopeValue, Scope apiScope, bool check)
|
|
||||||
{
|
|
||||||
return new ScopeViewModel
|
|
||||||
{
|
|
||||||
Value = parsedScopeValue.RawValue,
|
|
||||||
// todo: use the parsed scope value in the display?
|
|
||||||
DisplayName = apiScope.DisplayName,
|
|
||||||
Description = apiScope.Description,
|
|
||||||
Emphasize = parsedScopeValue.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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
// 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 nuget_host.Models
|
|
||||||
{
|
|
||||||
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<string[]>(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthenticateResult AuthenticateResult { get; }
|
|
||||||
public IEnumerable<string> Clients { get; } = new List<string>();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
using System;
|
|
||||||
using IdentityServer4.Models;
|
|
||||||
|
|
||||||
namespace nuget_host.Models
|
|
||||||
{
|
|
||||||
public class ErrorViewModel
|
|
||||||
{
|
|
||||||
public string RequestId { get; set; }
|
|
||||||
|
|
||||||
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
|
||||||
|
|
||||||
|
|
||||||
public ErrorViewModel()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public ErrorViewModel(string error)
|
|
||||||
{
|
|
||||||
Error = new ErrorMessage { Error = error };
|
|
||||||
}
|
|
||||||
|
|
||||||
public ErrorMessage Error { get; set; }
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
using IdentityServer4.Models;
|
|
||||||
|
|
||||||
namespace nuget_host.Models
|
|
||||||
{
|
|
||||||
public class RepositoryIdentityResource : IdentityResource
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,104 +0,0 @@
|
|||||||
@model ConsentViewModel
|
|
||||||
|
|
||||||
<div class="page-consent">
|
|
||||||
<div class="lead">
|
|
||||||
@if (Model.ClientLogoUrl != null)
|
|
||||||
{
|
|
||||||
<div class="client-logo"><img src="@Model.ClientLogoUrl"></div>
|
|
||||||
}
|
|
||||||
<h1>
|
|
||||||
@Model.ClientName
|
|
||||||
<small class="text-muted">is requesting your permission</small>
|
|
||||||
</h1>
|
|
||||||
<p>Uncheck the permissions you do not wish to grant.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<partial name="_ValidationSummary" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form asp-action="Index">
|
|
||||||
<input type="hidden" asp-for="ReturnUrl" />
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-8">
|
|
||||||
@if (Model.IdentityScopes.Any())
|
|
||||||
{
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<span class="glyphicon glyphicon-user"></span>
|
|
||||||
Personal Information
|
|
||||||
</div>
|
|
||||||
<ul class="list-group list-group-flush">
|
|
||||||
@foreach (var scope in Model.IdentityScopes)
|
|
||||||
{
|
|
||||||
<partial name="_ScopeListItem" model="@scope" />
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (Model.ApiScopes.Any())
|
|
||||||
{
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<span class="glyphicon glyphicon-tasks"></span>
|
|
||||||
Application Access
|
|
||||||
</div>
|
|
||||||
<ul class="list-group list-group-flush">
|
|
||||||
@foreach (var scope in Model.ApiScopes)
|
|
||||||
{
|
|
||||||
<partial name="_ScopeListItem" model="scope" />
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<span class="glyphicon glyphicon-tasks"></span>
|
|
||||||
Description
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<input class="form-control" placeholder="Description or name of device" asp-for="Description" autofocus>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (Model.AllowRememberConsent)
|
|
||||||
{
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" asp-for="RememberConsent">
|
|
||||||
<label class="form-check-label" asp-for="RememberConsent">
|
|
||||||
<strong>Remember My Decision</strong>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-4">
|
|
||||||
<button name="button" value="yes" class="btn btn-primary" autofocus>Yes, Allow</button>
|
|
||||||
<button name="button" value="no" class="btn btn-secondary">No, Do Not Allow</button>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-4 col-lg-auto">
|
|
||||||
@if (Model.ClientUrl != null)
|
|
||||||
{
|
|
||||||
<a class="btn btn-outline-info" href="@Model.ClientUrl">
|
|
||||||
<span class="glyphicon glyphicon-info-sign"></span>
|
|
||||||
<strong>@Model.ClientName</strong>
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
@ -1,7 +0,0 @@
|
|||||||
|
|
||||||
<div class="page-device-success">
|
|
||||||
<div class="lead">
|
|
||||||
<h1>Success</h1>
|
|
||||||
<p>You have successfully authorized the device</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,23 +0,0 @@
|
|||||||
@model string
|
|
||||||
|
|
||||||
<div class="page-device-code">
|
|
||||||
<div class="lead">
|
|
||||||
<h1>User Code</h1>
|
|
||||||
<p>Please enter the code displayed on your device.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<partial name="_ValidationSummary" />
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<form asp-action="UserCodeCapture">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="userCode">User Code:</label>
|
|
||||||
<input class="form-control" for="userCode" name="userCode" autofocus />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="btn btn-primary" name="button">Submit</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,108 +0,0 @@
|
|||||||
@model DeviceAuthorizationViewModel
|
|
||||||
|
|
||||||
<div class="page-device-confirmation">
|
|
||||||
<div class="lead">
|
|
||||||
@if (Model.ClientLogoUrl != null)
|
|
||||||
{
|
|
||||||
<div class="client-logo"><img src="@Model.ClientLogoUrl"></div>
|
|
||||||
}
|
|
||||||
<h1>
|
|
||||||
@Model.ClientName
|
|
||||||
<small class="text-muted">is requesting your permission</small>
|
|
||||||
</h1>
|
|
||||||
@if (Model.ConfirmUserCode)
|
|
||||||
{
|
|
||||||
<p>Please confirm that the authorization request quotes the code: <strong>@Model.UserCode</strong>.</p>
|
|
||||||
}
|
|
||||||
<p>Uncheck the permissions you do not wish to grant.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-8">
|
|
||||||
<partial name="_ValidationSummary" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form asp-action="Callback">
|
|
||||||
<input asp-for="UserCode" type="hidden" value="@Model.UserCode" />
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-8">
|
|
||||||
@if (Model.IdentityScopes.Any())
|
|
||||||
{
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<span class="glyphicon glyphicon-user"></span>
|
|
||||||
Personal Information
|
|
||||||
</div>
|
|
||||||
<ul class="list-group list-group-flush">
|
|
||||||
@foreach (var scope in Model.IdentityScopes)
|
|
||||||
{
|
|
||||||
<partial name="_ScopeListItem" model="@scope" />
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (Model.ApiScopes.Any())
|
|
||||||
{
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<span class="glyphicon glyphicon-tasks"></span>
|
|
||||||
Application Access
|
|
||||||
</div>
|
|
||||||
<ul class="list-group list-group-flush">
|
|
||||||
@foreach (var scope in Model.ApiScopes)
|
|
||||||
{
|
|
||||||
<partial name="_ScopeListItem" model="scope" />
|
|
||||||
}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<span class="glyphicon glyphicon-tasks"></span>
|
|
||||||
Description
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<input class="form-control" placeholder="Description or name of device" asp-for="Description" autofocus>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (Model.AllowRememberConsent)
|
|
||||||
{
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="form-check">
|
|
||||||
<input class="form-check-input" asp-for="RememberConsent">
|
|
||||||
<label class="form-check-label" asp-for="RememberConsent">
|
|
||||||
<strong>Remember My Decision</strong>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-4">
|
|
||||||
<button name="button" value="yes" class="btn btn-primary" autofocus>Yes, Allow</button>
|
|
||||||
<button name="button" value="no" class="btn btn-secondary">No, Do Not Allow</button>
|
|
||||||
</div>
|
|
||||||
<div class="col-sm-4 col-lg-auto">
|
|
||||||
@if (Model.ClientUrl != null)
|
|
||||||
{
|
|
||||||
<a class="btn btn-outline-info" href="@Model.ClientUrl">
|
|
||||||
<span class="glyphicon glyphicon-info-sign"></span>
|
|
||||||
<strong>@Model.ClientName</strong>
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
@ -1,64 +0,0 @@
|
|||||||
@model DiagnosticsViewModel
|
|
||||||
|
|
||||||
<div class="diagnostics-page">
|
|
||||||
<div class="lead">
|
|
||||||
<h1>Authentication Cookie</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h2>Claims</h2>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<dl>
|
|
||||||
@foreach (var claim in Model.AuthenticateResult.Principal.Claims)
|
|
||||||
{
|
|
||||||
<dt>@claim.Type</dt>
|
|
||||||
<dd>@claim.Value</dd>
|
|
||||||
}
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h2>Properties</h2>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
|
||||||
<dl>
|
|
||||||
@foreach (var prop in Model.AuthenticateResult.Properties.Items)
|
|
||||||
{
|
|
||||||
<dt>@prop.Key</dt>
|
|
||||||
<dd>@prop.Value</dd>
|
|
||||||
}
|
|
||||||
@if (Model.Clients.Any())
|
|
||||||
{
|
|
||||||
<dt>Clients</dt>
|
|
||||||
<dd>
|
|
||||||
@{
|
|
||||||
var clients = Model.Clients.ToArray();
|
|
||||||
for(var i = 0; i < clients.Length; i++)
|
|
||||||
{
|
|
||||||
<text>@clients[i]</text>
|
|
||||||
if (i < clients.Length - 1)
|
|
||||||
{
|
|
||||||
<text>, </text>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</dd>
|
|
||||||
}
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,33 +1,11 @@
|
|||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Home Page";
|
ViewData["Title"] = "Home Page";}
|
||||||
var version = FileVersionInfo.GetVersionInfo(typeof(IdentityServer4.Hosting.IdentityServerMiddleware).Assembly.Location).ProductVersion.Split('+').First();
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<h1 class="display-4">Welcome</h1>
|
<h1 class="display-4">Welcome</h1>
|
||||||
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
|
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">building Web apps with ASP.NET Core</a>.</p>
|
||||||
<h1>
|
<h1>
|
||||||
<img src="~/icon.jpg">
|
<img src="~/icon.jpg">
|
||||||
Welcome to IdentityServer4
|
Welcome to Nuget host
|
||||||
<small class="text-muted">(version @version)</small>
|
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
IdentityServer publishes a
|
|
||||||
<a href="~/.well-known/openid-configuration">discovery document</a>
|
|
||||||
where you can find metadata and links to all the endpoints, key material, etc.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Click <a href="~/diagnostics">here</a> to see the claims for your current session.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Click <a href="~/grants">here</a> to manage your stored grants.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Here are links to the
|
|
||||||
<a href="https://github.com/identityserver/IdentityServer4">source code repository</a>,
|
|
||||||
and <a href="https://github.com/IdentityServer/IdentityServer4/tree/main/samples">ready to use samples</a>.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
@model ErrorViewModel
|
|
||||||
|
|
||||||
@{
|
|
||||||
var error = Model?.Error?.Error;
|
|
||||||
var errorDescription = Model?.Error?.ErrorDescription;
|
|
||||||
var request_id = Model?.Error?.RequestId;
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="error-page">
|
|
||||||
<div class="lead">
|
|
||||||
<h1>Error</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<div class="alert alert-danger">
|
|
||||||
Sorry, there was an error
|
|
||||||
|
|
||||||
@if (error != null)
|
|
||||||
{
|
|
||||||
<strong>
|
|
||||||
<em>
|
|
||||||
: @error
|
|
||||||
</em>
|
|
||||||
</strong>
|
|
||||||
|
|
||||||
if (errorDescription != null)
|
|
||||||
{
|
|
||||||
<div>@errorDescription</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (request_id != null)
|
|
||||||
{
|
|
||||||
<div class="request-id">Request Id: @request_id</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
@ -1,34 +0,0 @@
|
|||||||
@model ScopeViewModel
|
|
||||||
|
|
||||||
<li class="list-group-item">
|
|
||||||
<label>
|
|
||||||
<input class="consent-scopecheck"
|
|
||||||
type="checkbox"
|
|
||||||
name="ScopesConsented"
|
|
||||||
id="scopes_@Model.Value"
|
|
||||||
value="@Model.Value"
|
|
||||||
checked="@Model.Checked"
|
|
||||||
disabled="@Model.Required" />
|
|
||||||
@if (Model.Required)
|
|
||||||
{
|
|
||||||
<input type="hidden"
|
|
||||||
name="ScopesConsented"
|
|
||||||
value="@Model.Value" />
|
|
||||||
}
|
|
||||||
<strong>@Model.DisplayName</strong>
|
|
||||||
@if (Model.Emphasize)
|
|
||||||
{
|
|
||||||
<span class="glyphicon glyphicon-exclamation-sign"></span>
|
|
||||||
}
|
|
||||||
</label>
|
|
||||||
@if (Model.Required)
|
|
||||||
{
|
|
||||||
<span><em>(required)</em></span>
|
|
||||||
}
|
|
||||||
@if (Model.Description != null)
|
|
||||||
{
|
|
||||||
<div class="consent-description">
|
|
||||||
<label for="scopes_@Model.Value">@Model.Description</label>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</li>
|
|
Loading…
Reference in New Issue