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": { |
|||
"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 Volo.Abp.IdentityServer; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace Volo.Abp.Account.Web |
|||
{ |
|||
[DependsOn(typeof(AbpAccountWebModule))] |
|||
[DependsOn(typeof(AbpIdentityServerDomainModule))] |
|||
public class AbpAccountWebIdentityServerModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(IServiceCollection services) |
|||
{ |
|||
services.AddAssemblyOf<AbpAccountWebModule>(); |
|||
|
|||
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