mirror of https://github.com/abpframework/abp.git
committed by
GitHub
46 changed files with 1492 additions and 67 deletions
@ -0,0 +1,20 @@ |
|||||
|
@page |
||||
|
@model MicroserviceDemo.AuthServer.Pages.IndexModel |
||||
|
|
||||
|
<h3>Welcome to the Authentication Server!</h3> |
||||
|
|
||||
|
<abp-card> |
||||
|
<abp-card-header></abp-card-header> |
||||
|
<abp-card-body> |
||||
|
<a href="~/Account/Login">Login</a> |
||||
|
|
||||
|
@if (User.Identity.IsAuthenticated) |
||||
|
{ |
||||
|
foreach (var claim in User.Claims) |
||||
|
{ |
||||
|
<div>@claim.Type = @claim.Value</div> |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
</abp-card-body> |
||||
|
</abp-card> |
||||
@ -0,0 +1,17 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.AspNetCore.Mvc.RazorPages; |
||||
|
|
||||
|
namespace MicroserviceDemo.AuthServer.Pages |
||||
|
{ |
||||
|
public class IndexModel : PageModel |
||||
|
{ |
||||
|
public void OnGet() |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,5 +1,12 @@ |
|||||
{ |
{ |
||||
"ConnectionStrings": { |
"ConnectionStrings": { |
||||
"Default": "Server=localhost;Database=MicroservicesDemo.Web;Trusted_Connection=True;MultipleActiveResultSets=true" |
"Default": "Server=localhost;Database=MicroservicesDemo.Web;Trusted_Connection=True;MultipleActiveResultSets=true" |
||||
|
}, |
||||
|
"Authentication": { |
||||
|
"Facebook": { |
||||
|
"IsEnabled": "true", |
||||
|
"AppId": "911417875702990", |
||||
|
"AppSecret": "adea0bff222ae340d8fb0ce3e6275d6b" |
||||
|
} |
||||
} |
} |
||||
} |
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<OutputType>Exe</OutputType> |
||||
|
<TargetFramework>netcoreapp2.0</TargetFramework> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="IdentityModel" Version="3.1.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,54 @@ |
|||||
|
using System; |
||||
|
using System.Net.Http; |
||||
|
using System.Threading.Tasks; |
||||
|
using IdentityModel.Client; |
||||
|
|
||||
|
namespace MicroserviceDemo.ConsoleClient |
||||
|
{ |
||||
|
class Program |
||||
|
{ |
||||
|
static void Main(string[] args) |
||||
|
{ |
||||
|
RunDemo().Wait(); |
||||
|
Console.ReadLine(); |
||||
|
} |
||||
|
|
||||
|
private static async Task RunDemo() |
||||
|
{ |
||||
|
// discover endpoints from metadata
|
||||
|
var disco = await DiscoveryClient.GetAsync("http://localhost:54307"); |
||||
|
if (disco.IsError) |
||||
|
{ |
||||
|
Console.WriteLine(disco.Error); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// request token
|
||||
|
var tokenClient = new TokenClient(disco.TokenEndpoint, "client", "secret"); |
||||
|
var tokenResponse = await tokenClient.RequestClientCredentialsAsync("multi-tenancy-api"); |
||||
|
|
||||
|
if (tokenResponse.IsError) |
||||
|
{ |
||||
|
Console.WriteLine(tokenResponse.Error); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
Console.WriteLine(tokenResponse.Json); |
||||
|
|
||||
|
// call api
|
||||
|
var client = new HttpClient(); |
||||
|
client.SetBearerToken(tokenResponse.AccessToken); |
||||
|
|
||||
|
var response = await client.GetAsync("http://localhost:63877/api/multi-tenancy/tenant?SkipCount=0&MaxResultCount=100"); |
||||
|
if (!response.IsSuccessStatusCode) |
||||
|
{ |
||||
|
Console.WriteLine(response.StatusCode); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var content = await response.Content.ReadAsStringAsync(); |
||||
|
Console.WriteLine(content); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Authentication; |
||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
using Volo.Abp.AspNetCore.Mvc; |
||||
|
|
||||
|
namespace MicroserviceDemo.Web.Controllers |
||||
|
{ |
||||
|
public class AccountController : AbpController |
||||
|
{ |
||||
|
public async Task Logout() |
||||
|
{ |
||||
|
await HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme); |
||||
|
await HttpContext.SignOutAsync("oidc"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,20 +1,22 @@ |
|||||
using Microsoft.Extensions.DependencyInjection; |
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.IdentityServer; |
||||
using Volo.Abp.Modularity; |
using Volo.Abp.Modularity; |
||||
using Volo.Abp.VirtualFileSystem; |
using Volo.Abp.VirtualFileSystem; |
||||
|
|
||||
namespace Volo.Abp.Account.Web |
namespace Volo.Abp.Account.Web |
||||
{ |
{ |
||||
[DependsOn(typeof(AbpAccountWebModule))] |
[DependsOn(typeof(AbpAccountWebModule))] |
||||
|
[DependsOn(typeof(AbpIdentityServerDomainModule))] |
||||
public class AbpAccountWebIdentityServerModule : AbpModule |
public class AbpAccountWebIdentityServerModule : AbpModule |
||||
{ |
{ |
||||
public override void ConfigureServices(IServiceCollection services) |
public override void ConfigureServices(IServiceCollection services) |
||||
{ |
{ |
||||
services.AddAssemblyOf<AbpAccountWebModule>(); |
|
||||
|
|
||||
services.Configure<VirtualFileSystemOptions>(options => |
services.Configure<VirtualFileSystemOptions>(options => |
||||
{ |
{ |
||||
options.FileSets.AddEmbedded<AbpAccountWebModule>("Volo.Abp.Account.Web"); |
options.FileSets.AddEmbedded<AbpAccountWebIdentityServerModule>("Volo.Abp.Account.Web"); |
||||
}); |
}); |
||||
|
|
||||
|
services.AddAssemblyOf<AbpAccountWebIdentityServerModule>(); |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -0,0 +1,33 @@ |
|||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
|
||||
|
namespace Volo.Abp.Account.Web.Auth |
||||
|
{ |
||||
|
//TODO: Move to the framework..?
|
||||
|
public static class SignInResultExtensions |
||||
|
{ |
||||
|
public static string GetResultAsString(this SignInResult signInResult) |
||||
|
{ |
||||
|
if (signInResult.Succeeded) |
||||
|
{ |
||||
|
return "Succeeded"; |
||||
|
} |
||||
|
|
||||
|
if (signInResult.IsLockedOut) |
||||
|
{ |
||||
|
return "IsLockedOut"; |
||||
|
} |
||||
|
|
||||
|
if (signInResult.IsNotAllowed) |
||||
|
{ |
||||
|
return "IsNotAllowed"; |
||||
|
} |
||||
|
|
||||
|
if (signInResult.RequiresTwoFactor) |
||||
|
{ |
||||
|
return "RequiresTwoFactor"; |
||||
|
} |
||||
|
|
||||
|
return "Unknown"; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,82 @@ |
|||||
|
@page |
||||
|
@using Volo.Abp.Account.Web.Pages.Account |
||||
|
@model GrantsModel |
||||
|
|
||||
|
@* TODO: Should work on the page HTML, copy/pasted from IDS4 samples *@ |
||||
|
|
||||
|
<div class="grants"> |
||||
|
<div class="row page-header"> |
||||
|
<div class="col-sm-10"> |
||||
|
<h1> |
||||
|
Client Application Access |
||||
|
</h1> |
||||
|
<div>Below is the list of applications you have given access to and the names of the resources they have access to.</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
@if (Model.Grants.Any() == false) |
||||
|
{ |
||||
|
<div class="row"> |
||||
|
<div class="col-sm-8"> |
||||
|
<div class="alert alert-info"> |
||||
|
You have not given access to any applications |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
foreach (var grant in Model.Grants) |
||||
|
{ |
||||
|
<div class="row grant"> |
||||
|
<div class="col-sm-2"> |
||||
|
@if (grant.ClientLogoUrl != null) |
||||
|
{ |
||||
|
<img src="@grant.ClientLogoUrl"> |
||||
|
} |
||||
|
</div> |
||||
|
<div class="col-sm-8"> |
||||
|
<div class="clientname">@grant.ClientName</div> |
||||
|
<div> |
||||
|
<span class="created">Created:</span> @grant.Created.ToString("yyyy-MM-dd") |
||||
|
</div> |
||||
|
@if (grant.Expires.HasValue) |
||||
|
{ |
||||
|
<div> |
||||
|
<span class="expires">Expires:</span> @grant.Expires.Value.ToString("yyyy-MM-dd") |
||||
|
</div> |
||||
|
} |
||||
|
@if (grant.IdentityGrantNames.Any()) |
||||
|
{ |
||||
|
<div> |
||||
|
<div class="granttype">Identity Grants</div> |
||||
|
<ul> |
||||
|
@foreach (var name in grant.IdentityGrantNames) |
||||
|
{ |
||||
|
<li>@name</li> |
||||
|
} |
||||
|
</ul> |
||||
|
</div> |
||||
|
} |
||||
|
@if (grant.ApiGrantNames.Any()) |
||||
|
{ |
||||
|
<div> |
||||
|
<div class="granttype">API Grants</div> |
||||
|
<ul> |
||||
|
@foreach (var name in grant.ApiGrantNames) |
||||
|
{ |
||||
|
<li>@name</li> |
||||
|
} |
||||
|
</ul> |
||||
|
</div> |
||||
|
} |
||||
|
</div> |
||||
|
<div class="col-sm-2"> |
||||
|
<form asp-page="./Grants" asp-page-handler="Revoke" method="post"> |
||||
|
<input type="hidden" name="clientId" value="@grant.ClientId"> |
||||
|
<button class="btn btn-danger">Revoke Access</button> |
||||
|
</form> |
||||
|
</div> |
||||
|
</div> |
||||
|
} |
||||
|
} |
||||
|
</div> |
||||
@ -0,0 +1,75 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using IdentityServer4.Services; |
||||
|
using IdentityServer4.Stores; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Volo.Abp.AspNetCore.Mvc.RazorPages; |
||||
|
|
||||
|
namespace Volo.Abp.Account.Web.Pages.Account |
||||
|
{ |
||||
|
public class GrantsModel : AbpPageModel |
||||
|
{ |
||||
|
public List<GrantViewModel> Grants { get; set; } |
||||
|
|
||||
|
private readonly IIdentityServerInteractionService _interaction; |
||||
|
private readonly IClientStore _clients; |
||||
|
private readonly IResourceStore _resources; |
||||
|
|
||||
|
public GrantsModel(IIdentityServerInteractionService interaction, |
||||
|
IClientStore clients, |
||||
|
IResourceStore resources) |
||||
|
{ |
||||
|
_interaction = interaction; |
||||
|
_clients = clients; |
||||
|
_resources = resources; |
||||
|
} |
||||
|
|
||||
|
public async Task OnGet() |
||||
|
{ |
||||
|
Grants = new List<GrantViewModel>(); |
||||
|
|
||||
|
foreach (var consent in await _interaction.GetAllUserConsentsAsync()) |
||||
|
{ |
||||
|
var client = await _clients.FindClientByIdAsync(consent.ClientId); |
||||
|
if (client != null) |
||||
|
{ |
||||
|
var resources = await _resources.FindResourcesByScopeAsync(consent.Scopes); |
||||
|
|
||||
|
var item = new GrantViewModel |
||||
|
{ |
||||
|
ClientId = client.ClientId, |
||||
|
ClientName = client.ClientName ?? client.ClientId, |
||||
|
ClientLogoUrl = client.LogoUri, |
||||
|
ClientUrl = client.ClientUri, |
||||
|
Created = consent.CreationTime, |
||||
|
Expires = consent.Expiration, |
||||
|
IdentityGrantNames = resources.IdentityResources.Select(x => x.DisplayName ?? x.Name).ToArray(), |
||||
|
ApiGrantNames = resources.ApiResources.Select(x => x.DisplayName ?? x.Name).ToArray() |
||||
|
}; |
||||
|
|
||||
|
Grants.Add(item); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task<IActionResult> OnPostRevokeAsync(string clientId) |
||||
|
{ |
||||
|
await _interaction.RevokeUserConsentAsync(clientId); |
||||
|
return Redirect("/"); //TODO: ..?
|
||||
|
} |
||||
|
|
||||
|
public class GrantViewModel |
||||
|
{ |
||||
|
public string ClientId { get; set; } |
||||
|
public string ClientName { get; set; } |
||||
|
public string ClientUrl { get; set; } |
||||
|
public string ClientLogoUrl { get; set; } |
||||
|
public DateTime Created { get; set; } |
||||
|
public DateTime? Expires { get; set; } |
||||
|
public IEnumerable<string> IdentityGrantNames { get; set; } |
||||
|
public IEnumerable<string> ApiGrantNames { get; set; } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,61 @@ |
|||||
|
@page |
||||
|
@model Volo.Abp.Account.Web.Pages.Account.IdsLoginModel |
||||
|
<div class="row"> |
||||
|
|
||||
|
@if (Model.EnableLocalLogin) |
||||
|
{ |
||||
|
<div class="col-md-6"> |
||||
|
<div class="col-md-4"> |
||||
|
<form method="post" asp-page="/Account/Login"> |
||||
|
<input asp-for="ReturnUrl" /> |
||||
|
<input asp-for="ReturnUrlHash" /> |
||||
|
<div class="form-group"> |
||||
|
<label asp-for="LoginInput.UserNameOrEmailAddress"></label> |
||||
|
<input asp-for="LoginInput.UserNameOrEmailAddress" class="form-control" /> |
||||
|
<span asp-validation-for="LoginInput.UserNameOrEmailAddress" class="text-danger"></span> |
||||
|
</div> |
||||
|
<div class="form-group"> |
||||
|
<label asp-for="LoginInput.Password"></label> |
||||
|
<input asp-for="LoginInput.Password" class="form-control" /> |
||||
|
<span asp-validation-for="LoginInput.Password" class="text-danger"></span> |
||||
|
</div> |
||||
|
<div class="form-check"> |
||||
|
<label asp-for="LoginInput.RememberMe" class="form-check-label"> |
||||
|
<input asp-for="LoginInput.RememberMe" class="form-check-input" /> |
||||
|
@Html.DisplayNameFor(m => m.LoginInput.RememberMe) |
||||
|
</label> |
||||
|
</div> |
||||
|
<button type="submit" class="btn btn-primary" name="Action" value="Login">Login</button> |
||||
|
<button type="button" class="btn btn-secondary" name="Action" value="Cancel">Cancel</button> @* TODO: Only show if identity server is used *@ |
||||
|
</form> |
||||
|
|
||||
|
<div style="padding-top: 20px"> |
||||
|
<a href="@Url.Page("./Register", new {returnUrl = Model.ReturnUrl, returnUrlHash = Model.ReturnUrlHash})">Register</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
} |
||||
|
|
||||
|
@if (Model.VisibleExternalProviders.Any()) |
||||
|
{ |
||||
|
<div class="col-md-6"> |
||||
|
<h4>Use another service to log in.</h4> |
||||
|
<form asp-page="./Login" asp-page-handler="ExternalLogin" asp-route-returnUrl="@Model.ReturnUrl" asp-route-returnUrlHash="@Model.ReturnUrlHash" method="post"> |
||||
|
<input asp-for="ReturnUrl" /> |
||||
|
<input asp-for="ReturnUrlHash" /> |
||||
|
@foreach (var provider in Model.VisibleExternalProviders) |
||||
|
{ |
||||
|
<button type="submit" class="btn btn-primary" name="provider" value="@provider.AuthenticationScheme" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button> |
||||
|
} |
||||
|
</form> |
||||
|
</div> |
||||
|
} |
||||
|
|
||||
|
@if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any()) |
||||
|
{ |
||||
|
<div class="alert alert-warning"> |
||||
|
<strong>Invalid login request</strong> |
||||
|
There are no login schemes configured for this client. |
||||
|
</div> |
||||
|
} |
||||
|
</div> |
||||
@ -0,0 +1,315 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
using System.Diagnostics; |
||||
|
using System.Linq; |
||||
|
using System.Security.Claims; |
||||
|
using System.Security.Principal; |
||||
|
using System.Threading.Tasks; |
||||
|
using IdentityModel; |
||||
|
using IdentityServer4.Events; |
||||
|
using IdentityServer4.Models; |
||||
|
using IdentityServer4.Services; |
||||
|
using IdentityServer4.Stores; |
||||
|
using Microsoft.AspNetCore.Authentication; |
||||
|
using Microsoft.AspNetCore.Identity; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Microsoft.Extensions.Logging; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp.Account.Web.Auth; |
||||
|
using Volo.Abp.Identity; |
||||
|
using Volo.Abp.Ui; |
||||
|
using Volo.Abp.Uow; |
||||
|
|
||||
|
namespace Volo.Abp.Account.Web.Pages.Account |
||||
|
{ |
||||
|
//TODO: Inherit from LoginModel of Account.Web project. We should design it as extensible.
|
||||
|
public class IdsLoginModel : AccountModelBase |
||||
|
{ |
||||
|
[HiddenInput] |
||||
|
[BindProperty(SupportsGet = true)] |
||||
|
public string ReturnUrl { get; set; } |
||||
|
|
||||
|
[HiddenInput] |
||||
|
[BindProperty(SupportsGet = true)] |
||||
|
public string ReturnUrlHash { get; set; } |
||||
|
|
||||
|
[BindProperty] |
||||
|
public LoginInputModel LoginInput { get; set; } |
||||
|
|
||||
|
public bool EnableLocalLogin { get; set; } |
||||
|
|
||||
|
//TODO: Why there is an ExternalProviders if only the VisibleExternalProviders is used.
|
||||
|
public IEnumerable<ExternalProviderModel> ExternalProviders { get; set; } |
||||
|
public IEnumerable<ExternalProviderModel> VisibleExternalProviders => ExternalProviders.Where(x => !String.IsNullOrWhiteSpace(x.DisplayName)); |
||||
|
|
||||
|
public bool IsExternalLoginOnly => EnableLocalLogin == false && ExternalProviders?.Count() == 1; |
||||
|
public string ExternalLoginScheme => IsExternalLoginOnly ? ExternalProviders?.SingleOrDefault()?.AuthenticationScheme : null; |
||||
|
|
||||
|
private readonly SignInManager<IdentityUser> _signInManager; |
||||
|
private readonly IdentityUserManager _userManager; |
||||
|
private readonly IIdentityServerInteractionService _interaction; |
||||
|
private readonly IAuthenticationSchemeProvider _schemeProvider; |
||||
|
private readonly AbpAccountOptions _accountOptions; |
||||
|
private readonly IClientStore _clientStore; |
||||
|
private readonly IEventService _identityServerEvents; |
||||
|
|
||||
|
public IdsLoginModel( |
||||
|
SignInManager<IdentityUser> signInManager, |
||||
|
IdentityUserManager userManager, |
||||
|
IIdentityServerInteractionService interaction, |
||||
|
IAuthenticationSchemeProvider schemeProvider, |
||||
|
IOptions<AbpAccountOptions> accountOptions, |
||||
|
IClientStore clientStore, |
||||
|
IEventService identityServerEvents) |
||||
|
{ |
||||
|
_signInManager = signInManager; |
||||
|
_userManager = userManager; |
||||
|
_interaction = interaction; |
||||
|
_schemeProvider = schemeProvider; |
||||
|
_clientStore = clientStore; |
||||
|
_identityServerEvents = identityServerEvents; |
||||
|
_accountOptions = accountOptions.Value; |
||||
|
} |
||||
|
|
||||
|
public async Task OnGetAsync() |
||||
|
{ |
||||
|
LoginInput = new LoginInputModel(); |
||||
|
|
||||
|
var context = await _interaction.GetAuthorizationContextAsync(ReturnUrl); |
||||
|
LoginInput.UserNameOrEmailAddress = context?.LoginHint; |
||||
|
|
||||
|
if (context?.IdP != null) |
||||
|
{ |
||||
|
LoginInput.UserNameOrEmailAddress = context.LoginHint; |
||||
|
ExternalProviders = new[] {new ExternalProviderModel {AuthenticationScheme = context.IdP}}; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var schemes = await _schemeProvider.GetAllSchemesAsync(); |
||||
|
|
||||
|
var providers = schemes |
||||
|
.Where(x => x.DisplayName != null || x.Name.Equals(_accountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase)) |
||||
|
.Select(x => new ExternalProviderModel |
||||
|
{ |
||||
|
DisplayName = x.DisplayName, |
||||
|
AuthenticationScheme = x.Name |
||||
|
}) |
||||
|
.ToList(); |
||||
|
|
||||
|
EnableLocalLogin = true; //TODO: We can get default from a setting?
|
||||
|
if (context?.ClientId != null) |
||||
|
{ |
||||
|
var client = await _clientStore.FindEnabledClientByIdAsync(context.ClientId); |
||||
|
if (client != null) |
||||
|
{ |
||||
|
EnableLocalLogin = client.EnableLocalLogin; |
||||
|
|
||||
|
if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any()) |
||||
|
{ |
||||
|
providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
ExternalProviders = providers.ToArray(); |
||||
|
|
||||
|
if (IsExternalLoginOnly) |
||||
|
{ |
||||
|
//return await ExternalLogin(vm.ExternalLoginScheme, returnUrl);
|
||||
|
throw new NotImplementedException(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
[UnitOfWork] //TODO: Will be removed when we implement action filter
|
||||
|
public virtual async Task<IActionResult> OnPostAsync(string action) |
||||
|
{ |
||||
|
if (action == "Cancel") |
||||
|
{ |
||||
|
var context = await _interaction.GetAuthorizationContextAsync(ReturnUrl); |
||||
|
if (context == null) |
||||
|
{ |
||||
|
return Redirect("~/"); |
||||
|
} |
||||
|
|
||||
|
await _interaction.GrantConsentAsync(context, ConsentResponse.Denied); |
||||
|
return Redirect(ReturnUrl); |
||||
|
} |
||||
|
|
||||
|
ValidateModel(); |
||||
|
|
||||
|
var result = await _signInManager.PasswordSignInAsync( |
||||
|
LoginInput.UserNameOrEmailAddress, |
||||
|
LoginInput.Password, |
||||
|
LoginInput.RememberMe, |
||||
|
true |
||||
|
); |
||||
|
|
||||
|
//TODO: Find a way of getting user's id from the logged in user and do not query it again like that!
|
||||
|
var user = await _userManager.FindByNameAsync(LoginInput.UserNameOrEmailAddress) ?? |
||||
|
await _userManager.FindByEmailAsync(LoginInput.UserNameOrEmailAddress); |
||||
|
|
||||
|
if (!result.Succeeded) |
||||
|
{ |
||||
|
await _identityServerEvents.RaiseAsync(new UserLoginFailureEvent(LoginInput.UserNameOrEmailAddress, "Login failed: " + result.GetResultAsString())); |
||||
|
throw new UserFriendlyException("Login failed!"); //TODO: Handle other cases, do not throw exception
|
||||
|
} |
||||
|
|
||||
|
Debug.Assert(user != null, nameof(user) + " != null"); |
||||
|
await _identityServerEvents.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id.ToString(), user.UserName)); //TODO: Use user's name once implemented
|
||||
|
|
||||
|
if (!_interaction.IsValidReturnUrl(ReturnUrl)) |
||||
|
{ |
||||
|
return Redirect("~/"); |
||||
|
} |
||||
|
|
||||
|
return Redirect(ReturnUrl); //ReturnUrlHash?
|
||||
|
} |
||||
|
|
||||
|
[UnitOfWork] |
||||
|
public virtual async Task<IActionResult> OnPostExternalLogin(string provider) |
||||
|
{ |
||||
|
if (_accountOptions.WindowsAuthenticationSchemeName == provider) |
||||
|
{ |
||||
|
return await ProcessWindowsLoginAsync(); |
||||
|
} |
||||
|
|
||||
|
var redirectUrl = Url.Page("./Login", pageHandler: "ExternalLoginCallback", values: new { ReturnUrl, ReturnUrlHash }); |
||||
|
var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); |
||||
|
properties.Items["scheme"] = provider; |
||||
|
|
||||
|
return Challenge(properties, provider); |
||||
|
} |
||||
|
|
||||
|
[UnitOfWork] |
||||
|
public virtual async Task<IActionResult> OnGetExternalLoginCallbackAsync(string returnUrl = "", string returnUrlHash = "", string remoteError = null) |
||||
|
{ |
||||
|
//TODO: Did not implemented Identity Server 4 sample for this method (see ExternalLoginCallback in Quickstart of IDS4 sample)
|
||||
|
/* Also did not implement these: |
||||
|
* - Logout(string logoutId) |
||||
|
*/ |
||||
|
|
||||
|
if (remoteError != null) |
||||
|
{ |
||||
|
Logger.LogWarning($"External login callback error: {remoteError}"); |
||||
|
return RedirectToPage("./Login"); |
||||
|
} |
||||
|
|
||||
|
var loginInfo = await _signInManager.GetExternalLoginInfoAsync(); |
||||
|
if (loginInfo == null) |
||||
|
{ |
||||
|
Logger.LogWarning("External login info is not available"); |
||||
|
return RedirectToPage("./Login"); |
||||
|
} |
||||
|
|
||||
|
var result = await _signInManager.ExternalLoginSignInAsync( |
||||
|
loginInfo.LoginProvider, |
||||
|
loginInfo.ProviderKey, |
||||
|
isPersistent: false, |
||||
|
bypassTwoFactor: true |
||||
|
); |
||||
|
|
||||
|
if (result.IsLockedOut) |
||||
|
{ |
||||
|
throw new UserFriendlyException("Cannot proceed because user is locked out!"); |
||||
|
} |
||||
|
|
||||
|
if (result.Succeeded) |
||||
|
{ |
||||
|
return RedirectSafely(returnUrl, returnUrlHash); |
||||
|
} |
||||
|
|
||||
|
//TODO: Handle other cases for result!
|
||||
|
|
||||
|
// Get the information about the user from the external login provider
|
||||
|
var info = await _signInManager.GetExternalLoginInfoAsync(); |
||||
|
if (info == null) |
||||
|
{ |
||||
|
throw new ApplicationException("Error loading external login information during confirmation."); |
||||
|
} |
||||
|
|
||||
|
var user = await CreateExternalUserAsync(info); |
||||
|
|
||||
|
await _signInManager.SignInAsync(user, false); |
||||
|
return RedirectSafely(returnUrl, returnUrlHash); |
||||
|
} |
||||
|
|
||||
|
private async Task<IdentityUser> CreateExternalUserAsync(ExternalLoginInfo info) |
||||
|
{ |
||||
|
var emailAddress = info.Principal.FindFirstValue(ClaimTypes.Email); |
||||
|
|
||||
|
var user = new IdentityUser(GuidGenerator.Create(), emailAddress, CurrentTenant.Id); |
||||
|
|
||||
|
CheckIdentityErrors(await _userManager.CreateAsync(user)); |
||||
|
CheckIdentityErrors(await _userManager.SetEmailAsync(user, emailAddress)); |
||||
|
CheckIdentityErrors(await _userManager.AddLoginAsync(user, info)); |
||||
|
|
||||
|
return user; |
||||
|
} |
||||
|
|
||||
|
private async Task<IActionResult> ProcessWindowsLoginAsync() |
||||
|
{ |
||||
|
var result = await HttpContext.AuthenticateAsync(_accountOptions.WindowsAuthenticationSchemeName); |
||||
|
if (!(result?.Principal is WindowsPrincipal windowsPrincipal)) |
||||
|
{ |
||||
|
return Challenge(_accountOptions.WindowsAuthenticationSchemeName); |
||||
|
} |
||||
|
|
||||
|
var props = new AuthenticationProperties |
||||
|
{ |
||||
|
RedirectUri = Url.Page("./Login", pageHandler: "ExternalLoginCallback", values: new { ReturnUrl, ReturnUrlHash }), |
||||
|
Items = |
||||
|
{ |
||||
|
{"scheme", _accountOptions.WindowsAuthenticationSchemeName}, |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
var identity = new ClaimsIdentity(_accountOptions.WindowsAuthenticationSchemeName); |
||||
|
identity.AddClaim(new Claim(JwtClaimTypes.Subject, windowsPrincipal.Identity.Name)); |
||||
|
identity.AddClaim(new Claim(JwtClaimTypes.Name, windowsPrincipal.Identity.Name)); |
||||
|
|
||||
|
//TODO: Consider to add Windows groups the the identity
|
||||
|
//if (_accountOptions.IncludeWindowsGroups)
|
||||
|
//{
|
||||
|
// var windowsIdentity = windowsPrincipal.Identity as WindowsIdentity;
|
||||
|
// if (windowsIdentity != null)
|
||||
|
// {
|
||||
|
// var groups = windowsIdentity.Groups?.Translate(typeof(NTAccount));
|
||||
|
// var roles = groups.Select(x => new Claim(JwtClaimTypes.Role, x.Value));
|
||||
|
// identity.AddClaims(roles);
|
||||
|
// }
|
||||
|
//}
|
||||
|
|
||||
|
await HttpContext.SignInAsync( |
||||
|
IdentityServer4.IdentityServerConstants.ExternalCookieAuthenticationScheme, |
||||
|
new ClaimsPrincipal(identity), |
||||
|
props |
||||
|
); |
||||
|
|
||||
|
return RedirectSafely(props.RedirectUri); |
||||
|
} |
||||
|
|
||||
|
public class LoginInputModel |
||||
|
{ |
||||
|
[Required] |
||||
|
[StringLength(255)] |
||||
|
[Display(Name = "UserNameOrEmailAddress")] |
||||
|
public string UserNameOrEmailAddress { get; set; } |
||||
|
|
||||
|
[Required] |
||||
|
[StringLength(32)] |
||||
|
[DataType(DataType.Password)] |
||||
|
[Display(Name = "Password")] |
||||
|
public string Password { get; set; } |
||||
|
|
||||
|
[Display(Name = "RememberMe")] |
||||
|
public bool RememberMe { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class ExternalProviderModel |
||||
|
{ |
||||
|
public string DisplayName { get; set; } |
||||
|
public string AuthenticationScheme { get; set; } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
@using Volo.Abp.Account.Web.Pages |
||||
|
@using Volo.Abp.Account.Web.Pages.Account |
||||
|
@model ConsentModel.ScopeViewModel |
||||
|
@* TODO: Should re-format this, just made copy/paste *@ |
||||
|
<li class="list-group-item"> |
||||
|
<div class="form-check"> |
||||
|
<label asp-for="Checked" class="form-check-label"> |
||||
|
<input asp-for="Checked" class="form-check-input" disabled="@Model.Required" /> |
||||
|
@Html.DisplayNameFor(m => m.Checked) |
||||
|
<input asp-for="Name" type="hidden" /> |
||||
|
<strong>@Model.DisplayName</strong> |
||||
|
@if (Model.Emphasize) |
||||
|
{ |
||||
|
<span class="glyphicon glyphicon-exclamation-sign"></span> |
||||
|
} |
||||
|
</label> |
||||
|
</div> |
||||
|
@if (Model.Required) |
||||
|
{ |
||||
|
<span><em>(required)</em></span> |
||||
|
} |
||||
|
@if (Model.Description != null) |
||||
|
{ |
||||
|
<div class="consent-description"> |
||||
|
<label for="scopes_@Model.Name">@Model.Description</label> |
||||
|
</div> |
||||
|
} |
||||
|
</li> |
||||
@ -0,0 +1,2 @@ |
|||||
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers |
||||
|
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap |
||||
@ -0,0 +1,113 @@ |
|||||
|
@page |
||||
|
@using Volo.Abp.Account.Web.Pages |
||||
|
@using Volo.Abp.Account.Web.Pages.Account |
||||
|
@model ConsentModel |
||||
|
<abp-card id="IdentityServerConsentWrapper"> |
||||
|
<abp-card-header> |
||||
|
<div class="row"> |
||||
|
<div class="col-md-12"> |
||||
|
<h2> |
||||
|
@if (Model.ClientInfo.ClientLogoUrl != null) |
||||
|
{ |
||||
|
<img src="@Model.ClientInfo.ClientLogoUrl"> |
||||
|
} |
||||
|
|
||||
|
@Model.ClientInfo.ClientName |
||||
|
<small>is requesting your permission</small> |
||||
|
</h2> |
||||
|
</div> |
||||
|
</div> |
||||
|
</abp-card-header> |
||||
|
<abp-card-body> |
||||
|
<form method="post" asp-page="/Consent"> |
||||
|
<input type="hidden" asp-for="ReturnUrl" /> |
||||
|
<input type="hidden" asp-for="ReturnUrlHash" /> |
||||
|
|
||||
|
<div>Uncheck the permissions you do not wish to grant.</div> |
||||
|
|
||||
|
@if (Model.ConsentInput.IdentityScopes.Any()) |
||||
|
{ |
||||
|
<h3>Personal Information</h3> |
||||
|
|
||||
|
<ul class="list-group"> |
||||
|
@for (var i = 0; i < Model.ConsentInput.IdentityScopes.Count; i++) |
||||
|
{ |
||||
|
<li class="list-group-item"> |
||||
|
<div class="form-check"> |
||||
|
<label asp-for="@Model.ConsentInput.IdentityScopes[i].Checked" class="form-check-label"> |
||||
|
<input asp-for="@Model.ConsentInput.IdentityScopes[i].Checked" class="form-check-input" /> |
||||
|
@Model.ConsentInput.IdentityScopes[i].DisplayName |
||||
|
@if (Model.ConsentInput.IdentityScopes[i].Required) |
||||
|
{ |
||||
|
<span><em>(required)</em></span> |
||||
|
} |
||||
|
</label> |
||||
|
</div> |
||||
|
<input asp-for="@Model.ConsentInput.IdentityScopes[i].Name" type="hidden" /> @* TODO: Use attributes on the view model instead of using hidden here *@ |
||||
|
@if (Model.ConsentInput.IdentityScopes[i].Description != null) |
||||
|
{ |
||||
|
<div class="consent-description"> |
||||
|
@Model.ConsentInput.IdentityScopes[i].Description |
||||
|
</div> |
||||
|
} |
||||
|
</li> |
||||
|
} |
||||
|
</ul> |
||||
|
} |
||||
|
|
||||
|
@if (Model.ConsentInput.ApiScopes.Any()) |
||||
|
{ |
||||
|
<h3>Application Access</h3> |
||||
|
|
||||
|
<ul class="list-group"> |
||||
|
@for (var i = 0; i < Model.ConsentInput.ApiScopes.Count; i++) |
||||
|
{ |
||||
|
<li class="list-group-item"> |
||||
|
<div class="form-check"> |
||||
|
<label asp-for="@Model.ConsentInput.ApiScopes[i].Checked" class="form-check-label"> |
||||
|
<input asp-for="@Model.ConsentInput.ApiScopes[i].Checked" class="form-check-input" disabled="@Model.ConsentInput.ApiScopes[i].Required" /> |
||||
|
@Model.ConsentInput.ApiScopes[i].DisplayName |
||||
|
@if (Model.ConsentInput.ApiScopes[i].Required) |
||||
|
{ |
||||
|
<span><em>(required)</em></span> |
||||
|
} |
||||
|
</label> |
||||
|
</div> |
||||
|
<input asp-for="@Model.ConsentInput.ApiScopes[i].Name" type="hidden" /> @* TODO: Use attributes on the view model instead of using hidden here *@ |
||||
|
@if (Model.ConsentInput.ApiScopes[i].Description != null) |
||||
|
{ |
||||
|
<div class="consent-description"> |
||||
|
@Model.ConsentInput.ApiScopes[i].Description |
||||
|
</div> |
||||
|
} |
||||
|
</li> |
||||
|
} |
||||
|
</ul> |
||||
|
} |
||||
|
|
||||
|
@if (Model.ClientInfo.AllowRememberConsent) |
||||
|
{ |
||||
|
<div class="form-check"> |
||||
|
<label asp-for="@Model.ConsentInput.RememberConsent" class="form-check-label"> |
||||
|
<input asp-for="@Model.ConsentInput.RememberConsent" class="form-check-input" /> |
||||
|
<strong>Remember My Decision</strong> |
||||
|
</label> |
||||
|
</div> |
||||
|
} |
||||
|
|
||||
|
<div> |
||||
|
<button name="UserDecision" value="yes" class="btn btn-primary" autofocus>Yes, Allow</button> |
||||
|
<button name="UserDecision" value="no" class="btn">No, Do Not Allow</button> |
||||
|
@if (Model.ClientInfo.ClientUrl != null) |
||||
|
{ |
||||
|
<a class="pull-right btn btn-secondary" target="_blank" href="@Model.ClientInfo.ClientUrl"> |
||||
|
<strong>@Model.ClientInfo.ClientName</strong> |
||||
|
</a> |
||||
|
} |
||||
|
</div> |
||||
|
|
||||
|
<div asp-validation-summary="All" class="text-danger"></div> |
||||
|
|
||||
|
</form> |
||||
|
</abp-card-body> |
||||
|
</abp-card> |
||||
@ -0,0 +1,240 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using IdentityServer4.Models; |
||||
|
using IdentityServer4.Services; |
||||
|
using IdentityServer4.Stores; |
||||
|
using Microsoft.AspNetCore.Mvc; |
||||
|
using Volo.Abp.AspNetCore.Mvc.RazorPages; |
||||
|
using Volo.Abp.Ui; |
||||
|
|
||||
|
namespace Volo.Abp.Account.Web.Pages |
||||
|
{ |
||||
|
//TODO: Move this into the Account folder!!!
|
||||
|
public class ConsentModel : AbpPageModel |
||||
|
{ |
||||
|
[HiddenInput] |
||||
|
[BindProperty(SupportsGet = true)] |
||||
|
public string ReturnUrl { get; set; } |
||||
|
|
||||
|
[HiddenInput] |
||||
|
[BindProperty(SupportsGet = true)] |
||||
|
public string ReturnUrlHash { get; set; } |
||||
|
|
||||
|
[BindProperty] |
||||
|
public ConsentModel.ConsentInputModel ConsentInput { get; set; } |
||||
|
|
||||
|
public ClientInfoModel ClientInfo { get; set; } |
||||
|
|
||||
|
private readonly IIdentityServerInteractionService _interaction; |
||||
|
private readonly IClientStore _clientStore; |
||||
|
private readonly IResourceStore _resourceStore; |
||||
|
|
||||
|
public ConsentModel( |
||||
|
IIdentityServerInteractionService interaction, |
||||
|
IClientStore clientStore, |
||||
|
IResourceStore resourceStore) |
||||
|
{ |
||||
|
_interaction = interaction; |
||||
|
_clientStore = clientStore; |
||||
|
_resourceStore = resourceStore; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task OnGet() |
||||
|
{ |
||||
|
var request = await _interaction.GetAuthorizationContextAsync(ReturnUrl); |
||||
|
if (request == null) |
||||
|
{ |
||||
|
throw new ApplicationException($"No consent request matching request: {ReturnUrl}"); |
||||
|
} |
||||
|
|
||||
|
var client = await _clientStore.FindEnabledClientByIdAsync(request.ClientId); |
||||
|
if (client == null) |
||||
|
{ |
||||
|
throw new ApplicationException($"Invalid client id: {request.ClientId}"); |
||||
|
} |
||||
|
|
||||
|
var resources = await _resourceStore.FindEnabledResourcesByScopeAsync(request.ScopesRequested); |
||||
|
if (resources == null || (!resources.IdentityResources.Any() && !resources.ApiResources.Any())) |
||||
|
{ |
||||
|
throw new ApplicationException($"No scopes matching: {request.ScopesRequested.Aggregate((x, y) => x + ", " + y)}"); |
||||
|
} |
||||
|
|
||||
|
ClientInfo = new ClientInfoModel(client); |
||||
|
ConsentInput = new ConsentInputModel |
||||
|
{ |
||||
|
RememberConsent = true, |
||||
|
IdentityScopes = resources.IdentityResources.Select(x => CreateScopeViewModel(x, true)).ToList(), |
||||
|
ApiScopes = resources.ApiResources.SelectMany(x => x.Scopes).Select(x => CreateScopeViewModel(x, true)).ToList() |
||||
|
}; |
||||
|
|
||||
|
if (resources.OfflineAccess) |
||||
|
{ |
||||
|
ConsentInput.ApiScopes.Add(GetOfflineAccessScope(true)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<IActionResult> OnPost(string userDecision) |
||||
|
{ |
||||
|
var result = await ProcessConsentAsync(); |
||||
|
|
||||
|
if (result.IsRedirect) |
||||
|
{ |
||||
|
return Redirect(result.RedirectUri); |
||||
|
} |
||||
|
|
||||
|
if (result.HasValidationError) |
||||
|
{ |
||||
|
//ModelState.AddModelError("", result.ValidationError);
|
||||
|
throw new ApplicationException("Error: " + result.ValidationError); |
||||
|
} |
||||
|
|
||||
|
throw new ApplicationException("Unknown Error!"); |
||||
|
} |
||||
|
|
||||
|
protected virtual async Task<ConsentModel.ProcessConsentResult> ProcessConsentAsync() |
||||
|
{ |
||||
|
var result = new ConsentModel.ProcessConsentResult(); |
||||
|
|
||||
|
ConsentResponse grantedConsent; |
||||
|
|
||||
|
if (ConsentInput.UserDecision == "no") |
||||
|
{ |
||||
|
grantedConsent = ConsentResponse.Denied; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
if (ConsentInput.IdentityScopes.Any() || ConsentInput.ApiScopes.Any()) |
||||
|
{ |
||||
|
grantedConsent = new ConsentResponse |
||||
|
{ |
||||
|
RememberConsent = ConsentInput.RememberConsent, |
||||
|
ScopesConsented = ConsentInput.GetAllowedScopeNames() |
||||
|
}; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
throw new UserFriendlyException("You must pick at least one permission"); //TODO: How to handle this
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (grantedConsent != null) |
||||
|
{ |
||||
|
var request = await _interaction.GetAuthorizationContextAsync(ReturnUrl); |
||||
|
if (request == null) |
||||
|
{ |
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
await _interaction.GrantConsentAsync(request, grantedConsent); |
||||
|
|
||||
|
result.RedirectUri = ReturnUrl; //TODO: ReturnUrlHash?
|
||||
|
} |
||||
|
|
||||
|
return result; |
||||
|
} |
||||
|
|
||||
|
protected virtual ConsentModel.ScopeViewModel CreateScopeViewModel(IdentityResource identity, bool check) |
||||
|
{ |
||||
|
return new ConsentModel.ScopeViewModel |
||||
|
{ |
||||
|
Name = identity.Name, |
||||
|
DisplayName = identity.DisplayName, |
||||
|
Description = identity.Description, |
||||
|
Emphasize = identity.Emphasize, |
||||
|
Required = identity.Required, |
||||
|
Checked = check || identity.Required |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
protected virtual ConsentModel.ScopeViewModel CreateScopeViewModel(Scope scope, bool check) |
||||
|
{ |
||||
|
return new ConsentModel.ScopeViewModel |
||||
|
{ |
||||
|
Name = scope.Name, |
||||
|
DisplayName = scope.DisplayName, |
||||
|
Description = scope.Description, |
||||
|
Emphasize = scope.Emphasize, |
||||
|
Required = scope.Required, |
||||
|
Checked = check || scope.Required |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
protected virtual ConsentModel.ScopeViewModel GetOfflineAccessScope(bool check) |
||||
|
{ |
||||
|
return new ConsentModel.ScopeViewModel |
||||
|
{ |
||||
|
Name = IdentityServer4.IdentityServerConstants.StandardScopes.OfflineAccess, |
||||
|
DisplayName = "Offline Access", //TODO: Localize
|
||||
|
Description = "Access to your applications and resources, even when you are offline", |
||||
|
Emphasize = true, |
||||
|
Checked = check |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
public class ConsentInputModel |
||||
|
{ |
||||
|
public List<ConsentModel.ScopeViewModel> IdentityScopes { get; set; } |
||||
|
|
||||
|
public List<ConsentModel.ScopeViewModel> ApiScopes { get; set; } |
||||
|
|
||||
|
[Required] |
||||
|
public string UserDecision { get; set; } |
||||
|
|
||||
|
public bool RememberConsent { get; set; } |
||||
|
|
||||
|
public List<string> GetAllowedScopeNames() |
||||
|
{ |
||||
|
return IdentityScopes.Union(ApiScopes).Where(s => s.Checked).Select(s => s.Name).ToList(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class ScopeViewModel |
||||
|
{ |
||||
|
[Required] |
||||
|
[HiddenInput] |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public bool Checked { get; set; } |
||||
|
|
||||
|
public string DisplayName { get; set; } |
||||
|
|
||||
|
public string Description { get; set; } |
||||
|
|
||||
|
public bool Emphasize { get; set; } |
||||
|
|
||||
|
public bool Required { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class ProcessConsentResult |
||||
|
{ |
||||
|
public bool IsRedirect => RedirectUri != null; |
||||
|
public string RedirectUri { get; set; } |
||||
|
|
||||
|
public bool HasValidationError => ValidationError != null; |
||||
|
public string ValidationError { get; set; } |
||||
|
} |
||||
|
|
||||
|
public class ClientInfoModel |
||||
|
{ |
||||
|
public string ClientName { get; set; } |
||||
|
|
||||
|
public string ClientUrl { get; set; } |
||||
|
|
||||
|
public string ClientLogoUrl { get; set; } |
||||
|
|
||||
|
public bool AllowRememberConsent { get; set; } |
||||
|
|
||||
|
public ClientInfoModel(Client client) |
||||
|
{ |
||||
|
//TODO: Automap
|
||||
|
ClientName = client.ClientId; |
||||
|
ClientUrl = client.ClientUri; |
||||
|
ClientLogoUrl = client.LogoUri; |
||||
|
AllowRememberConsent = client.AllowRememberConsent; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers |
||||
|
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI |
||||
|
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap |
||||
@ -0,0 +1,16 @@ |
|||||
|
namespace Volo.Abp.Account.Web |
||||
|
{ |
||||
|
public class AbpAccountOptions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Default value: <see cref="Microsoft.AspNetCore.Server.IISIntegration.IISDefaults.AuthenticationScheme"/>.
|
||||
|
/// </summary>
|
||||
|
public string WindowsAuthenticationSchemeName { get; set; } |
||||
|
|
||||
|
public AbpAccountOptions() |
||||
|
{ |
||||
|
//TODO: This makes us depend on the Microsoft.AspNetCore.Server.IISIntegration package.
|
||||
|
WindowsAuthenticationSchemeName = Microsoft.AspNetCore.Server.IISIntegration.IISDefaults.AuthenticationScheme; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,2 @@ |
|||||
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers |
||||
|
@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap |
||||
@ -0,0 +1,27 @@ |
|||||
|
using Volo.Abp.AspNetCore.Authentication.OAuth.Claims; |
||||
|
using Volo.Abp.Security.Claims; |
||||
|
|
||||
|
namespace Microsoft.AspNetCore.Authentication.OAuth.Claims |
||||
|
{ |
||||
|
public static class AbpClaimActionCollectionExtensions |
||||
|
{ |
||||
|
public static void MapAbpClaimTypes(this ClaimActionCollection claimActions) |
||||
|
{ |
||||
|
claimActions.MapJsonKey(AbpClaimTypes.Email, "email"); |
||||
|
claimActions.MapJsonKey(AbpClaimTypes.UserName, "name"); |
||||
|
claimActions.MapJsonKey(AbpClaimTypes.EmailVerified, "email_verified"); |
||||
|
claimActions.MapJsonKey(AbpClaimTypes.PhoneNumber, "phone_number"); |
||||
|
claimActions.MapJsonKey(AbpClaimTypes.PhoneNumberVerified, "phone_number_verified"); |
||||
|
|
||||
|
claimActions.MapJsonKeyMultiple(AbpClaimTypes.Role, "role"); |
||||
|
|
||||
|
claimActions.DeleteClaim("name"); |
||||
|
claimActions.DeleteClaim("email"); |
||||
|
} |
||||
|
|
||||
|
public static void MapJsonKeyMultiple(this ClaimActionCollection claimActions, string claimType, string jsonKey) |
||||
|
{ |
||||
|
claimActions.Add(new MultipleClaimAction(claimType, jsonKey)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netstandard2.0</TargetFramework> |
||||
|
<AssemblyName>Volo.Abp.AspNetCore.Authentication.OAuth</AssemblyName> |
||||
|
<PackageId>Volo.Abp.AspNetCore.Authentication.OAuth</PackageId> |
||||
|
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" /> |
||||
|
<ProjectReference Include="..\Volo.Abp.Security\Volo.Abp.Security.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.OAuth" Version="2.0.1" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,15 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.Security; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.Authentication.OAuth |
||||
|
{ |
||||
|
[DependsOn(typeof(AbpSecurityModule))] |
||||
|
public class AbpAspNetCoreAuthenticationOAuthModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(IServiceCollection services) |
||||
|
{ |
||||
|
services.AddAssemblyOf<AbpAspNetCoreAuthenticationOAuthModule>(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
using System.Security.Claims; |
||||
|
using Microsoft.AspNetCore.Authentication.OAuth.Claims; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.Authentication.OAuth.Claims |
||||
|
{ |
||||
|
public class MultipleClaimAction : ClaimAction |
||||
|
{ |
||||
|
public MultipleClaimAction(string claimType, string jsonKey) |
||||
|
: base(claimType, jsonKey) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public override void Run(JObject userData, ClaimsIdentity identity, string issuer) |
||||
|
{ |
||||
|
var prop = userData?.Property(ValueType); |
||||
|
if (prop == null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
var propValue = prop.Value; |
||||
|
|
||||
|
switch (propValue.Type) |
||||
|
{ |
||||
|
case JTokenType.String: |
||||
|
identity.AddClaim(new Claim(ClaimType, propValue.Value<string>(), ValueType, issuer)); |
||||
|
break; |
||||
|
case JTokenType.Array: |
||||
|
foreach (var innterValue in propValue.Values<string>()) |
||||
|
{ |
||||
|
identity.AddClaim(new Claim(ClaimType, innterValue, ValueType, issuer)); |
||||
|
} |
||||
|
break; |
||||
|
default: |
||||
|
throw new AbpException("Unhandled JTokenType: " + propValue.Type); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>netcoreapp2.0</TargetFramework> |
||||
|
<LangVersion>latest</LangVersion> |
||||
|
<AssemblyName>Volo.Abp.AspNetCore.Authentication.OAuth.Tests</AssemblyName> |
||||
|
<PackageId>Volo.Abp.AspNetCore.Authentication.OAuth.Tests</PackageId> |
||||
|
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Volo.Abp.AspNetCore.Authentication.OAuth\Volo.Abp.AspNetCore.Authentication.OAuth.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,46 @@ |
|||||
|
using System.Linq; |
||||
|
using System.Security.Claims; |
||||
|
using Newtonsoft.Json.Linq; |
||||
|
using Shouldly; |
||||
|
using Volo.Abp.Security.Claims; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Volo.Abp.AspNetCore.Authentication.OAuth.Claims |
||||
|
{ |
||||
|
public class MultipleClaimAction_Tests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Should_Set_Single_Value() |
||||
|
{ |
||||
|
var jObject = JObject.Parse(@"{
|
||||
|
""sub"": ""71054539-0e48-af28-5e7a-39e4e42d8ea5"", |
||||
|
""role"": ""admin"" |
||||
|
}");
|
||||
|
|
||||
|
var claimsIdentity = new ClaimsIdentity(); |
||||
|
new MultipleClaimAction(AbpClaimTypes.Role, "role").Run(jObject, claimsIdentity, null); |
||||
|
var claims = claimsIdentity.FindAll(AbpClaimTypes.Role).ToList(); |
||||
|
claims.Count.ShouldBe(1); |
||||
|
claims[0].Value.ShouldBe("admin"); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Set_Multiple_Values() |
||||
|
{ |
||||
|
var jObject = JObject.Parse(@"{
|
||||
|
""sub"": ""71054539-0e48-af28-5e7a-39e4e42d8ea5"", |
||||
|
""role"": [ |
||||
|
""admin"", |
||||
|
""moderator"" |
||||
|
] |
||||
|
}");
|
||||
|
|
||||
|
var claimsIdentity = new ClaimsIdentity(); |
||||
|
new MultipleClaimAction(AbpClaimTypes.Role, "role").Run(jObject, claimsIdentity, null); |
||||
|
var claims = claimsIdentity.FindAll(AbpClaimTypes.Role).ToList(); |
||||
|
claims.Count.ShouldBe(2); |
||||
|
claims[0].Value.ShouldBe("admin"); |
||||
|
claims[1].Value.ShouldBe("moderator"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue