diff --git a/.gitignore b/.gitignore index cf6f59024d..a20b3f93d9 100644 --- a/.gitignore +++ b/.gitignore @@ -257,4 +257,5 @@ src/AbpDesk/AbpDesk.Web.Mvc/Logs /src/Volo.Abp.Identity.HttpApi.Host/Logs src/MicroserviceDemo/MicroserviceDemo.Web/Logs/*.txt src/MicroserviceDemo/MicroserviceDemo.TenantService/Logs/*.txt -src/MicroserviceDemo/MicroserviceDemo.AuthServer/Logs/*.txt \ No newline at end of file +src/MicroserviceDemo/MicroserviceDemo.AuthServer/Logs/*.txt +src/MicroserviceDemo/MicroserviceDemo.TenancyService/Logs/*.txt diff --git a/Volo.Abp.sln b/Volo.Abp.sln index ee8cece3d9..d2aeef620b 100644 --- a/Volo.Abp.sln +++ b/Volo.Abp.sln @@ -296,9 +296,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicroserviceDemo.TenancySer EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.MultiTenancy.HttpApi.Client", "src\Volo.Abp.MultiTenancy.HttpApi.Client\Volo.Abp.MultiTenancy.HttpApi.Client.csproj", "{76D24E2C-8DB0-48B7-9FC4-02231B8B9F39}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicroserviceDemo.AuthServer", "src\MicroserviceDemo\MicroserviceDemo.AuthServer\MicroserviceDemo.AuthServer.csproj", "{A177B8B7-ACAD-4F75-B6D4-B72F7BC2E40A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicroserviceDemo.AuthServer", "src\MicroserviceDemo\MicroserviceDemo.AuthServer\MicroserviceDemo.AuthServer.csproj", "{A177B8B7-ACAD-4F75-B6D4-B72F7BC2E40A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Account.Web.IdentityServer", "src\Volo.Abp.Account.Web.IdentityServer\Volo.Abp.Account.Web.IdentityServer.csproj", "{E4AB8A4F-BB59-4BDB-B915-877CE97D8113}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Account.Web.IdentityServer", "src\Volo.Abp.Account.Web.IdentityServer\Volo.Abp.Account.Web.IdentityServer.csproj", "{E4AB8A4F-BB59-4BDB-B915-877CE97D8113}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicroserviceDemo.ConsoleClient", "src\MicroserviceDemo\MicroserviceDemo.ConsoleClient\MicroserviceDemo.ConsoleClient.csproj", "{CD4E755D-D47C-45B1-AFB3-3444FF2E2E39}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Authentication.OAuth", "src\Volo.Abp.AspNetCore.Authentication.OAuth\Volo.Abp.AspNetCore.Authentication.OAuth.csproj", "{A1C792B7-0DBF-460D-9158-A1A68A2D9C1A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.AspNetCore.Authentication.OAuth.Tests", "test\Volo.Abp.AspNetCore.Authentication.OAuth.Tests\Volo.Abp.AspNetCore.Authentication.OAuth.Tests.csproj", "{627B88DB-BDCF-4D92-8454-EFE95F4AFB7A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -790,6 +796,18 @@ Global {E4AB8A4F-BB59-4BDB-B915-877CE97D8113}.Debug|Any CPU.Build.0 = Debug|Any CPU {E4AB8A4F-BB59-4BDB-B915-877CE97D8113}.Release|Any CPU.ActiveCfg = Release|Any CPU {E4AB8A4F-BB59-4BDB-B915-877CE97D8113}.Release|Any CPU.Build.0 = Release|Any CPU + {CD4E755D-D47C-45B1-AFB3-3444FF2E2E39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CD4E755D-D47C-45B1-AFB3-3444FF2E2E39}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CD4E755D-D47C-45B1-AFB3-3444FF2E2E39}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CD4E755D-D47C-45B1-AFB3-3444FF2E2E39}.Release|Any CPU.Build.0 = Release|Any CPU + {A1C792B7-0DBF-460D-9158-A1A68A2D9C1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1C792B7-0DBF-460D-9158-A1A68A2D9C1A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1C792B7-0DBF-460D-9158-A1A68A2D9C1A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1C792B7-0DBF-460D-9158-A1A68A2D9C1A}.Release|Any CPU.Build.0 = Release|Any CPU + {627B88DB-BDCF-4D92-8454-EFE95F4AFB7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {627B88DB-BDCF-4D92-8454-EFE95F4AFB7A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {627B88DB-BDCF-4D92-8454-EFE95F4AFB7A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {627B88DB-BDCF-4D92-8454-EFE95F4AFB7A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -936,6 +954,9 @@ Global {76D24E2C-8DB0-48B7-9FC4-02231B8B9F39} = {CA154803-3589-47B3-B7CB-B18F94FE1EB6} {A177B8B7-ACAD-4F75-B6D4-B72F7BC2E40A} = {3510E248-DC9F-4A07-8134-02E7F5CC5783} {E4AB8A4F-BB59-4BDB-B915-877CE97D8113} = {DB012309-74FD-4D5A-B843-DD77BF053BF4} + {CD4E755D-D47C-45B1-AFB3-3444FF2E2E39} = {3510E248-DC9F-4A07-8134-02E7F5CC5783} + {A1C792B7-0DBF-460D-9158-A1A68A2D9C1A} = {C4C6961D-01CC-49A5-8B96-2A36E71CF01F} + {627B88DB-BDCF-4D92-8454-EFE95F4AFB7A} = {37087D1B-3693-4E96-983D-A69F210BDE53} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/src/AbpDesk/AbpDesk.Web.Mvc/AbpDeskWebMvcModule.cs b/src/AbpDesk/AbpDesk.Web.Mvc/AbpDeskWebMvcModule.cs index 10a9553113..7e414f93be 100644 --- a/src/AbpDesk/AbpDesk.Web.Mvc/AbpDeskWebMvcModule.cs +++ b/src/AbpDesk/AbpDesk.Web.Mvc/AbpDeskWebMvcModule.cs @@ -61,7 +61,6 @@ namespace AbpDesk.Web.Mvc services.PreConfigure(builder => { builder - .AddViewLocalization() //TODO: Move to the framework! .AddRazorPagesOptions(options => { options.Conventions.AuthorizeFolder("/App"); diff --git a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroserviceDemo.AuthServer.csproj b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroserviceDemo.AuthServer.csproj index 3661de16dd..2ffe2ec9c0 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroserviceDemo.AuthServer.csproj +++ b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroserviceDemo.AuthServer.csproj @@ -17,6 +17,7 @@ + diff --git a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs index 28837b8d08..dd0a0ccc12 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs +++ b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/MicroservicesAuthServerModule.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Builder; +using System.IO; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; @@ -6,12 +7,15 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp; using Volo.Abp.Account.Web; using Volo.Abp.AspNetCore.Modularity; +using Volo.Abp.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; using Volo.Abp.Autofac; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.Identity.EntityFrameworkCore; using Volo.Abp.IdentityServer.EntityFrameworkCore; using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; namespace MicroserviceDemo.AuthServer { @@ -43,6 +47,33 @@ namespace MicroserviceDemo.AuthServer }); }); + services.Configure(iis => + { + iis.AuthenticationDisplayName = "Windows"; + iis.AutomaticAuthentication = false; + }); + + if (hostingEnvironment.IsDevelopment()) + { + services.Configure(options => + { + options.FileSets.ReplaceEmbeddedByPyhsical(Path.Combine(hostingEnvironment.ContentRootPath, "..\\..\\Volo.Abp.AspNetCore.Mvc.UI")); + options.FileSets.ReplaceEmbeddedByPyhsical(Path.Combine(hostingEnvironment.ContentRootPath, "..\\..\\Volo.Abp.AspNetCore.Mvc.UI.Bootstrap")); + options.FileSets.ReplaceEmbeddedByPyhsical(Path.Combine(hostingEnvironment.ContentRootPath, "..\\..\\Volo.Abp.Account.Web")); + options.FileSets.ReplaceEmbeddedByPyhsical(Path.Combine(hostingEnvironment.ContentRootPath, "..\\..\\Volo.Abp.Account.Web.IdentityServer")); + }); + } + + services.AddAuthentication() + .AddFacebook(options => + { + options.AppId = configuration["Authentication:Facebook:AppId"]; + options.AppSecret = configuration["Authentication:Facebook:AppSecret"]; + + options.Scope.Add("email"); + options.Scope.Add("public_profile"); + }); + services.AddAssemblyOf(); } @@ -58,7 +89,19 @@ namespace MicroserviceDemo.AuthServer app.UseStaticFiles(); app.UseVirtualFiles(); - app.UseMvcWithDefaultRoute(); + app.UseIdentityServer(); //This internally adds .UseAuthentication() (we should be carefully about that) + + app.UseMvc(routes => + { + //TODO: Can we make an extension method for adding these two routes inside the framework? + routes.MapRoute( + name: "defaultWithArea", + template: "{area}/{controller=Home}/{action=Index}/{id?}"); + + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); } private static IConfigurationRoot BuildConfiguration(IHostingEnvironment env) diff --git a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/Pages/Index.cshtml b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/Pages/Index.cshtml new file mode 100644 index 0000000000..0712e36914 --- /dev/null +++ b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/Pages/Index.cshtml @@ -0,0 +1,20 @@ +@page +@model MicroserviceDemo.AuthServer.Pages.IndexModel + +

Welcome to the Authentication Server!

+ + + + + Login + + @if (User.Identity.IsAuthenticated) + { + foreach (var claim in User.Claims) + { +
@claim.Type = @claim.Value
+ } + } + +
+
\ No newline at end of file diff --git a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/Pages/Index.cshtml.cs b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/Pages/Index.cshtml.cs new file mode 100644 index 0000000000..0771c699c0 --- /dev/null +++ b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/Pages/Index.cshtml.cs @@ -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() + { + + } + } +} \ No newline at end of file diff --git a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/appsettings.json b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/appsettings.json index a3a7ec3fb7..2d740d39f2 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.AuthServer/appsettings.json +++ b/src/MicroserviceDemo/MicroserviceDemo.AuthServer/appsettings.json @@ -1,5 +1,12 @@ { "ConnectionStrings": { "Default": "Server=localhost;Database=MicroservicesDemo.Web;Trusted_Connection=True;MultipleActiveResultSets=true" + }, + "Authentication": { + "Facebook": { + "IsEnabled": "true", + "AppId": "911417875702990", + "AppSecret": "adea0bff222ae340d8fb0ce3e6275d6b" + } } } \ No newline at end of file diff --git a/src/MicroserviceDemo/MicroserviceDemo.ConsoleClient/MicroserviceDemo.ConsoleClient.csproj b/src/MicroserviceDemo/MicroserviceDemo.ConsoleClient/MicroserviceDemo.ConsoleClient.csproj new file mode 100644 index 0000000000..3d9798a90e --- /dev/null +++ b/src/MicroserviceDemo/MicroserviceDemo.ConsoleClient/MicroserviceDemo.ConsoleClient.csproj @@ -0,0 +1,12 @@ + + + + Exe + netcoreapp2.0 + + + + + + + diff --git a/src/MicroserviceDemo/MicroserviceDemo.ConsoleClient/Program.cs b/src/MicroserviceDemo/MicroserviceDemo.ConsoleClient/Program.cs new file mode 100644 index 0000000000..d2611feda0 --- /dev/null +++ b/src/MicroserviceDemo/MicroserviceDemo.ConsoleClient/Program.cs @@ -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); + } + } + } +} diff --git a/src/MicroserviceDemo/MicroserviceDemo.Web/Controllers/AccountController.cs b/src/MicroserviceDemo/MicroserviceDemo.Web/Controllers/AccountController.cs new file mode 100644 index 0000000000..91e40e5254 --- /dev/null +++ b/src/MicroserviceDemo/MicroserviceDemo.Web/Controllers/AccountController.cs @@ -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"); + } + } +} diff --git a/src/MicroserviceDemo/MicroserviceDemo.Web/MicroserviceDemo.Web.csproj b/src/MicroserviceDemo/MicroserviceDemo.Web/MicroserviceDemo.Web.csproj index ce06bc6d7a..7075a43b8c 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.Web/MicroserviceDemo.Web.csproj +++ b/src/MicroserviceDemo/MicroserviceDemo.Web/MicroserviceDemo.Web.csproj @@ -25,9 +25,8 @@ - + - diff --git a/src/MicroserviceDemo/MicroserviceDemo.Web/MicroservicesDemoWebModule.cs b/src/MicroserviceDemo/MicroserviceDemo.Web/MicroservicesDemoWebModule.cs index 7b91f3de5a..1eb5987748 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.Web/MicroservicesDemoWebModule.cs +++ b/src/MicroserviceDemo/MicroserviceDemo.Web/MicroservicesDemoWebModule.cs @@ -1,12 +1,15 @@ using System; +using Microsoft.AspNetCore.Authentication.OAuth.Claims; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Swashbuckle.AspNetCore.Swagger; using Volo.Abp; -using Volo.Abp.Account.Web; +using Volo.Abp.AspNetCore.Authentication.OAuth; using Volo.Abp.AspNetCore.Modularity; using Volo.Abp.AspNetCore.Mvc.Bundling; using Volo.Abp.Autofac; @@ -19,30 +22,20 @@ using Volo.Abp.Identity.Web; using Volo.Abp.Modularity; using Volo.Abp.MultiTenancy; using Volo.Abp.MultiTenancy.Web; -using Volo.Abp.Permissions; using Volo.Abp.Permissions.EntityFrameworkCore; namespace MicroserviceDemo.Web { [DependsOn(typeof(AbpAutofacModule))] - [DependsOn(typeof(AbpHttpClientModule))] [DependsOn(typeof(AbpPermissionsEntityFrameworkCoreModule))] [DependsOn(typeof(AbpIdentityHttpApiModule))] [DependsOn(typeof(AbpIdentityWebModule))] [DependsOn(typeof(AbpIdentityEntityFrameworkCoreModule))] - [DependsOn(typeof(AbpAccountWebModule))] [DependsOn(typeof(AbpMultiTenancyHttpApiClientModule))] [DependsOn(typeof(AbpMultiTenancyWebModule))] + [DependsOn(typeof(AbpAspNetCoreAuthenticationOAuthModule))] public class MicroservicesDemoWebModule : AbpModule { - public override void PreConfigureServices(IServiceCollection services) - { - services.PreConfigure(builder => - { - builder.AddViewLocalization(); //TODO: To the framework! - }); - } - public override void ConfigureServices(IServiceCollection services) { var hostingEnvironment = services.GetSingletonInstance(); @@ -74,8 +67,34 @@ namespace MicroserviceDemo.Web "/Abp/ServiceProxyScript?_v=" + DateTime.Now.Ticks }); }); + + services.AddAuthentication(options => + { + options.DefaultScheme = IdentityConstants.ApplicationScheme; + options.DefaultChallengeScheme = "oidc"; + }) + .AddOpenIdConnect("oidc", options => + { + options.SignInScheme = IdentityConstants.ApplicationScheme; + + options.Authority = "http://localhost:54307"; + options.RequireHttpsMetadata = false; - services.AddAuthentication(); + options.ClientId = "client"; + options.ClientSecret = "secret"; + + options.ResponseType = OpenIdConnectResponseType.CodeIdToken; + + options.SaveTokens = true; + options.GetClaimsFromUserInfoEndpoint = true; + + options.Scope.Add("role"); + options.Scope.Add("email"); + options.Scope.Add("phone"); + options.Scope.Add("multi-tenancy-api"); + + options.ClaimActions.MapAbpClaimTypes(); + }); services.Configure(configuration); @@ -109,7 +128,16 @@ namespace MicroserviceDemo.Web app.UseAuthentication(); - app.UseMvcWithDefaultRoute(); + app.UseMvc(routes => + { + routes.MapRoute( + name: "defaultWithArea", + template: "{area}/{controller=Home}/{action=Index}/{id?}"); + + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); } private static IConfigurationRoot BuildConfiguration(IHostingEnvironment env) diff --git a/src/MicroserviceDemo/MicroserviceDemo.Web/Pages/Index.cshtml b/src/MicroserviceDemo/MicroserviceDemo.Web/Pages/Index.cshtml index f61b9abf2f..7cc98887ca 100644 --- a/src/MicroserviceDemo/MicroserviceDemo.Web/Pages/Index.cshtml +++ b/src/MicroserviceDemo/MicroserviceDemo.Web/Pages/Index.cshtml @@ -1,11 +1,14 @@ @page +@using Microsoft.AspNetCore.Authentication +@using Microsoft.AspNetCore.Http @model MicroserviceDemo.Web.Pages.IndexModel +@inject IHttpContextAccessor HttpContextAccessor

Login - + @if (User.Identity.IsAuthenticated) { foreach (var claim in User.Claims) @@ -14,6 +17,18 @@ } } +


+ + @{ + var accessToken = await HttpContextAccessor.HttpContext.GetTokenAsync("access_token"); + var refreshToken = await HttpContextAccessor.HttpContext.GetTokenAsync("refresh_token"); + } + + accessToken = @accessToken
+ refreshToken = @refreshToken
+ + isInRole(admin) = @User.IsInRole("admin") +

\ No newline at end of file diff --git a/src/Volo.Abp.Account.Web.IdentityServer/AbpAccountWebIdentityServerModule.cs b/src/Volo.Abp.Account.Web.IdentityServer/AbpAccountWebIdentityServerModule.cs index 14e5100045..21ec67aa81 100644 --- a/src/Volo.Abp.Account.Web.IdentityServer/AbpAccountWebIdentityServerModule.cs +++ b/src/Volo.Abp.Account.Web.IdentityServer/AbpAccountWebIdentityServerModule.cs @@ -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(); - services.Configure(options => { - options.FileSets.AddEmbedded("Volo.Abp.Account.Web"); + options.FileSets.AddEmbedded("Volo.Abp.Account.Web"); }); + + services.AddAssemblyOf(); } } } diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Auth/SignInResultExtensions.cs b/src/Volo.Abp.Account.Web.IdentityServer/Auth/SignInResultExtensions.cs new file mode 100644 index 0000000000..ac229fbf52 --- /dev/null +++ b/src/Volo.Abp.Account.Web.IdentityServer/Auth/SignInResultExtensions.cs @@ -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"; + } + } +} diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Grants.cshtml b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Grants.cshtml new file mode 100644 index 0000000000..1f02ca8888 --- /dev/null +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Grants.cshtml @@ -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 *@ + +
+ + @if (Model.Grants.Any() == false) + { +
+
+
+ You have not given access to any applications +
+
+
+ } + else + { + foreach (var grant in Model.Grants) + { +
+
+ @if (grant.ClientLogoUrl != null) + { + + } +
+
+
@grant.ClientName
+
+ Created: @grant.Created.ToString("yyyy-MM-dd") +
+ @if (grant.Expires.HasValue) + { +
+ Expires: @grant.Expires.Value.ToString("yyyy-MM-dd") +
+ } + @if (grant.IdentityGrantNames.Any()) + { +
+
Identity Grants
+
    + @foreach (var name in grant.IdentityGrantNames) + { +
  • @name
  • + } +
+
+ } + @if (grant.ApiGrantNames.Any()) + { +
+
API Grants
+
    + @foreach (var name in grant.ApiGrantNames) + { +
  • @name
  • + } +
+
+ } +
+
+
+ + +
+
+
+ } + } +
\ No newline at end of file diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Grants.cshtml.cs b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Grants.cshtml.cs new file mode 100644 index 0000000000..eeb83453a6 --- /dev/null +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Grants.cshtml.cs @@ -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 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(); + + 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 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 IdentityGrantNames { get; set; } + public IEnumerable ApiGrantNames { get; set; } + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml new file mode 100644 index 0000000000..61364f038d --- /dev/null +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml @@ -0,0 +1,61 @@ +@page +@model Volo.Abp.Account.Web.Pages.Account.IdsLoginModel +
+ + @if (Model.EnableLocalLogin) + { +
+
+
+ + +
+ + + +
+
+ + + +
+
+ +
+ + @* TODO: Only show if identity server is used *@ +
+ +
+ Register +
+
+
+ } + + @if (Model.VisibleExternalProviders.Any()) + { +
+

Use another service to log in.

+
+ + + @foreach (var provider in Model.VisibleExternalProviders) + { + + } +
+
+ } + + @if (!Model.EnableLocalLogin && !Model.VisibleExternalProviders.Any()) + { +
+ Invalid login request + There are no login schemes configured for this client. +
+ } +
\ No newline at end of file diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs new file mode 100644 index 0000000000..a1db7969f1 --- /dev/null +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/Login.cshtml.cs @@ -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 ExternalProviders { get; set; } + public IEnumerable 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 _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 signInManager, + IdentityUserManager userManager, + IIdentityServerInteractionService interaction, + IAuthenticationSchemeProvider schemeProvider, + IOptions 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 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 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 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 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 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; } + } + } +} diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/_ScopeListItem.cshtml b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/_ScopeListItem.cshtml new file mode 100644 index 0000000000..95fee3a858 --- /dev/null +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/_ScopeListItem.cshtml @@ -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 *@ +
  • +
    + +
    + @if (Model.Required) + { + (required) + } + @if (Model.Description != null) + { + + } +
  • \ No newline at end of file diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/_ViewImports.cshtml b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/_ViewImports.cshtml new file mode 100644 index 0000000000..d1ac64721f --- /dev/null +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Account/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap \ No newline at end of file diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml new file mode 100644 index 0000000000..fa3efda621 --- /dev/null +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml @@ -0,0 +1,113 @@ +@page +@using Volo.Abp.Account.Web.Pages +@using Volo.Abp.Account.Web.Pages.Account +@model ConsentModel + + +
    +
    +

    + @if (Model.ClientInfo.ClientLogoUrl != null) + { + + } + + @Model.ClientInfo.ClientName + is requesting your permission +

    +
    +
    +
    + +
    + + + +
    Uncheck the permissions you do not wish to grant.
    + + @if (Model.ConsentInput.IdentityScopes.Any()) + { +

    Personal Information

    + +
      + @for (var i = 0; i < Model.ConsentInput.IdentityScopes.Count; i++) + { +
    • +
      + +
      + @* TODO: Use attributes on the view model instead of using hidden here *@ + @if (Model.ConsentInput.IdentityScopes[i].Description != null) + { + + } +
    • + } +
    + } + + @if (Model.ConsentInput.ApiScopes.Any()) + { +

    Application Access

    + +
      + @for (var i = 0; i < Model.ConsentInput.ApiScopes.Count; i++) + { +
    • +
      + +
      + @* TODO: Use attributes on the view model instead of using hidden here *@ + @if (Model.ConsentInput.ApiScopes[i].Description != null) + { + + } +
    • + } +
    + } + + @if (Model.ClientInfo.AllowRememberConsent) + { +
    + +
    + } + +
    + + + @if (Model.ClientInfo.ClientUrl != null) + { + + @Model.ClientInfo.ClientName + + } +
    + +
    + +
    +
    +
    \ No newline at end of file diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml.cs b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml.cs new file mode 100644 index 0000000000..4188b1b2a6 --- /dev/null +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/Consent.cshtml.cs @@ -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 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 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 IdentityScopes { get; set; } + + public List ApiScopes { get; set; } + + [Required] + public string UserDecision { get; set; } + + public bool RememberConsent { get; set; } + + public List 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; + } + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Pages/_ViewImports.cshtml b/src/Volo.Abp.Account.Web.IdentityServer/Pages/_ViewImports.cshtml new file mode 100644 index 0000000000..05e1baf060 --- /dev/null +++ b/src/Volo.Abp.Account.Web.IdentityServer/Pages/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap \ No newline at end of file diff --git a/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj b/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj index 5bedcf9979..32143716e5 100644 --- a/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj +++ b/src/Volo.Abp.Account.Web.IdentityServer/Volo.Abp.Account.Web.IdentityServer.csproj @@ -18,14 +18,10 @@ - - - - - - + + diff --git a/src/Volo.Abp.Account.Web/AbpAccountOptions.cs b/src/Volo.Abp.Account.Web/AbpAccountOptions.cs new file mode 100644 index 0000000000..21e72155cc --- /dev/null +++ b/src/Volo.Abp.Account.Web/AbpAccountOptions.cs @@ -0,0 +1,16 @@ +namespace Volo.Abp.Account.Web +{ + public class AbpAccountOptions + { + /// + /// Default value: . + /// + 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; + } + } +} diff --git a/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs b/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs index a0df66f747..e35f0050ed 100644 --- a/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs +++ b/src/Volo.Abp.Account.Web/AbpAccountWebModule.cs @@ -12,12 +12,12 @@ namespace Volo.Abp.Account.Web { public override void ConfigureServices(IServiceCollection services) { - services.AddAssemblyOf(); - services.Configure(options => { options.FileSets.AddEmbedded("Volo.Abp.Account.Web"); }); + + services.AddAssemblyOf(); } } } diff --git a/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs b/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs index 8c55d4737a..5ef29b6a1e 100644 --- a/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs +++ b/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Volo.Abp.Identity; +using Volo.Abp.Security.Claims; using Volo.Abp.Ui; using Volo.Abp.Uow; @@ -122,7 +123,7 @@ namespace Volo.Abp.Account.Web.Pages.Account private async Task CreateExternalUserAsync(ExternalLoginInfo info) { - var emailAddress = info.Principal.FindFirstValue(ClaimTypes.Email); + var emailAddress = info.Principal.FindFirstValue(AbpClaimTypes.Email); var user = new IdentityUser(GuidGenerator.Create(), emailAddress, CurrentTenant.Id); diff --git a/src/Volo.Abp.Account.Web/Pages/Account/_ViewImports.cshtml b/src/Volo.Abp.Account.Web/Pages/Account/_ViewImports.cshtml new file mode 100644 index 0000000000..d1ac64721f --- /dev/null +++ b/src/Volo.Abp.Account.Web/Pages/Account/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers +@addTagHelper *, Volo.Abp.AspNetCore.Mvc.UI.Bootstrap \ No newline at end of file diff --git a/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj b/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj index 6073769fc4..e844ba9f73 100644 --- a/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj +++ b/src/Volo.Abp.Account.Web/Volo.Abp.Account.Web.csproj @@ -19,6 +19,14 @@
    + + + + + + + + diff --git a/src/Volo.Abp.AspNetCore.Authentication.OAuth/Microsoft/AspNetCore/Authentication/OAuth/Claims/AbpClaimActionCollectionExtensions.cs b/src/Volo.Abp.AspNetCore.Authentication.OAuth/Microsoft/AspNetCore/Authentication/OAuth/Claims/AbpClaimActionCollectionExtensions.cs new file mode 100644 index 0000000000..dbaacb6949 --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Authentication.OAuth/Microsoft/AspNetCore/Authentication/OAuth/Claims/AbpClaimActionCollectionExtensions.cs @@ -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)); + } + } +} diff --git a/src/Volo.Abp.AspNetCore.Authentication.OAuth/Volo.Abp.AspNetCore.Authentication.OAuth.csproj b/src/Volo.Abp.AspNetCore.Authentication.OAuth/Volo.Abp.AspNetCore.Authentication.OAuth.csproj new file mode 100644 index 0000000000..d942b8cc0a --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Authentication.OAuth/Volo.Abp.AspNetCore.Authentication.OAuth.csproj @@ -0,0 +1,25 @@ + + + + + + netstandard2.0 + Volo.Abp.AspNetCore.Authentication.OAuth + Volo.Abp.AspNetCore.Authentication.OAuth + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + + + diff --git a/src/Volo.Abp.AspNetCore.Authentication.OAuth/Volo/Abp/AspNetCore/Authentication/OAuth/AbpAspNetCoreAuthenticationOAuthModule.cs b/src/Volo.Abp.AspNetCore.Authentication.OAuth/Volo/Abp/AspNetCore/Authentication/OAuth/AbpAspNetCoreAuthenticationOAuthModule.cs new file mode 100644 index 0000000000..35c75ed11c --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Authentication.OAuth/Volo/Abp/AspNetCore/Authentication/OAuth/AbpAspNetCoreAuthenticationOAuthModule.cs @@ -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(); + } + } +} diff --git a/src/Volo.Abp.AspNetCore.Authentication.OAuth/Volo/Abp/AspNetCore/Authentication/OAuth/Claims/MultipleClaimAction.cs b/src/Volo.Abp.AspNetCore.Authentication.OAuth/Volo/Abp/AspNetCore/Authentication/OAuth/Claims/MultipleClaimAction.cs new file mode 100644 index 0000000000..742f91d057 --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Authentication.OAuth/Volo/Abp/AspNetCore/Authentication/OAuth/Claims/MultipleClaimAction.cs @@ -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(), ValueType, issuer)); + break; + case JTokenType.Array: + foreach (var innterValue in propValue.Values()) + { + identity.AddClaim(new Claim(ClaimType, innterValue, ValueType, issuer)); + } + break; + default: + throw new AbpException("Unhandled JTokenType: " + propValue.Type); + } + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs index 8bb95c8817..aa4e373691 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs @@ -81,7 +81,8 @@ namespace Volo.Abp.AspNetCore.Mvc var resourceType = assemblyResources.GetOrDefault(type.Assembly); return factory.Create(resourceType ?? type); }; - }); + }) + .AddViewLocalization(); //TODO: How to configure from the application? Also, consider to move to a UI module since APIs does not care about it. services.ExecutePreConfiguredActions(mvcBuilder); diff --git a/src/Volo.Abp.Identity.Domain/Microsoft/Extensions/DependencyInjection/AbpIdentityServiceCollectionExtensions.cs b/src/Volo.Abp.Identity.Domain/Microsoft/Extensions/DependencyInjection/AbpIdentityServiceCollectionExtensions.cs index b3a1a73638..d9ee732225 100644 --- a/src/Volo.Abp.Identity.Domain/Microsoft/Extensions/DependencyInjection/AbpIdentityServiceCollectionExtensions.cs +++ b/src/Volo.Abp.Identity.Domain/Microsoft/Extensions/DependencyInjection/AbpIdentityServiceCollectionExtensions.cs @@ -37,7 +37,8 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddScoped(); services.TryAddScoped(typeof(IRoleStore), provider => provider.GetService(typeof(IdentityRoleStore))); - return services.AddIdentity(setupAction); + return services.AddIdentity(setupAction) + .AddDefaultTokenProviders(); //return services.AddIdentityCore(setupAction); } } diff --git a/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs b/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs index 87b5c0b51f..6e2472e1a6 100644 --- a/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs +++ b/src/Volo.Abp.Identity.Web/AbpIdentityWebModule.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; using Volo.Abp.AutoMapper; @@ -28,8 +29,6 @@ namespace Volo.Abp.Identity.Web public override void ConfigureServices(IServiceCollection services) { - services.AddAssemblyOf(); - services.Configure(options => { options.MenuContributors.Add(new AbpIdentityWebMainMenuContributor()); @@ -39,7 +38,7 @@ namespace Volo.Abp.Identity.Web { options.FileSets.AddEmbedded("Volo.Abp.Identity.Web"); }); - + services.Configure(options => { options.Resources.AddVirtualJson("en", "/Localization/Resources/AbpIdentity"); @@ -49,6 +48,18 @@ namespace Volo.Abp.Identity.Web { options.AddProfile(validate: true); }); + + services.Configure(options => + { + options.Conventions.AuthorizePage("/Identity/Users/Index", IdentityPermissions.Users.Default); + options.Conventions.AuthorizePage("/Identity/Users/CreateModal", IdentityPermissions.Users.Create); + options.Conventions.AuthorizePage("/Identity/Users/EditModal", IdentityPermissions.Users.Update); + options.Conventions.AuthorizePage("/Identity/Roles/Index", IdentityPermissions.Roles.Default); + options.Conventions.AuthorizePage("/Identity/Roles/CreateModal", IdentityPermissions.Roles.Create); + options.Conventions.AuthorizePage("/Identity/Roles/EditModal", IdentityPermissions.Roles.Update); + }); + + services.AddAssemblyOf(); } } } diff --git a/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpZeroIdentityServerBuilderExtensions.cs b/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerBuilderExtensions.cs similarity index 88% rename from src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpZeroIdentityServerBuilderExtensions.cs rename to src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerBuilderExtensions.cs index f8b52260bd..585b022b34 100644 --- a/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpZeroIdentityServerBuilderExtensions.cs +++ b/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerBuilderExtensions.cs @@ -10,7 +10,7 @@ using Volo.Abp.Security.Claims; namespace Volo.Abp.IdentityServer { - public static class AbpZeroIdentityServerBuilderExtensions + public static class AbpIdentityServerBuilderExtensions { public static IIdentityServerBuilder AddAbpIdentityServer( this IIdentityServerBuilder builder, @@ -31,6 +31,7 @@ namespace Volo.Abp.IdentityServer AbpClaimTypes.UserId = JwtClaimTypes.Subject; AbpClaimTypes.UserName = JwtClaimTypes.Name; AbpClaimTypes.Role = JwtClaimTypes.Role; + AbpClaimTypes.Email = JwtClaimTypes.Email; } if (options.UpdateJwtSecurityTokenHandlerDefaultInboundClaimTypeMap) @@ -38,6 +39,7 @@ namespace Volo.Abp.IdentityServer JwtSecurityTokenHandler.DefaultInboundClaimTypeMap[AbpClaimTypes.UserId] = AbpClaimTypes.UserId; JwtSecurityTokenHandler.DefaultInboundClaimTypeMap[AbpClaimTypes.UserName] = AbpClaimTypes.UserName; JwtSecurityTokenHandler.DefaultInboundClaimTypeMap[AbpClaimTypes.Role] = AbpClaimTypes.Role; + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap[AbpClaimTypes.Email] = AbpClaimTypes.Email; } return builder; diff --git a/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs b/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs index 3e49a826e6..99528c7d2d 100644 --- a/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs +++ b/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AbpIdentityServerDomainModule.cs @@ -2,7 +2,6 @@ using Volo.Abp.AutoMapper; using Volo.Abp.Identity; using Volo.Abp.IdentityServer.Clients; -using Volo.Abp.IdentityServer.Temp; using Volo.Abp.Modularity; using Volo.Abp.Security; @@ -22,19 +21,23 @@ namespace Volo.Abp.IdentityServer options.AddProfile(validate: true); }); - services.AddAssemblyOf(); - AddIdentityServer(services); + + services.AddAssemblyOf(); } private static void AddIdentityServer(IServiceCollection services) { - var identityServerBuilder = services.AddIdentityServer(); + var identityServerBuilder = services.AddIdentityServer(options => + { + options.Events.RaiseErrorEvents = true; + options.Events.RaiseInformationEvents = true; + options.Events.RaiseFailureEvents = true; + options.Events.RaiseSuccessEvents = true; + }); identityServerBuilder - .AddDeveloperSigningCredential() - //.AddInMemoryApiResources(IdentityServerConfig.GetApiResources()) - //.AddInMemoryClients(IdentityServerConfig.GetClients()) + .AddDeveloperSigningCredential() //TODO: Should be able to change this! .AddAbpIdentityServer(); services.ExecutePreConfiguredActions(identityServerBuilder); diff --git a/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpProfileService.cs b/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpProfileService.cs index c2cc261bc5..2c0dcafa47 100644 --- a/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpProfileService.cs +++ b/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/AspNetIdentity/AbpProfileService.cs @@ -9,26 +9,37 @@ namespace Volo.Abp.IdentityServer.AspNetIdentity { //TODO: Implement multi-tenancy as like in old ABP - public class AbpProfileService : ProfileService + public class AbpProfileService : ProfileService { + private readonly IUnitOfWorkManager _unitOfWorkManager; + public AbpProfileService( IdentityUserManager userManager, - IUserClaimsPrincipalFactory claimsFactory - ) : base(userManager, claimsFactory) + IUserClaimsPrincipalFactory claimsFactory, + IUnitOfWorkManager unitOfWorkManager) + : base(userManager, claimsFactory) { - + _unitOfWorkManager = unitOfWorkManager; } [UnitOfWork] public override async Task GetProfileDataAsync(ProfileDataRequestContext context) { - await base.GetProfileDataAsync(context); + using (var uow = _unitOfWorkManager.Begin()) + { + await base.GetProfileDataAsync(context); + await uow.CompleteAsync(); + } } [UnitOfWork] public override async Task IsActiveAsync(IsActiveContext context) { - await base.IsActiveAsync(context); + using (var uow = _unitOfWorkManager.Begin()) + { + await base.IsActiveAsync(context); + await uow.CompleteAsync(); + } } } } diff --git a/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientAutoMapperProfile.cs b/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientAutoMapperProfile.cs index 3d3393e7b8..81e5841d6b 100644 --- a/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientAutoMapperProfile.cs +++ b/src/Volo.Abp.IdentityServer.Domain/Volo/Abp/IdentityServer/Clients/ClientAutoMapperProfile.cs @@ -28,6 +28,11 @@ namespace Volo.Abp.IdentityServer.Clients CreateMap(); + CreateMap() + .ConstructUsing(src => src.Type) + .ReverseMap() + .ForMember(dest => dest.Type, opt => opt.MapFrom(src => src)); + CreateMap(); CreateMap(); diff --git a/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs b/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs index 79eaf25701..be363b8df4 100644 --- a/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs +++ b/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs @@ -2,33 +2,45 @@ namespace Volo.Abp.Security.Claims { + //TODO: Instead of directly using static properties, can we just create an AbpClaimOptions and pass these values as defaults? /// /// Used to get ABP-specific claim type names. /// public static class AbpClaimTypes { /// - /// UserId. /// Default: /// public static string UserName { get; set; } = ClaimTypes.Name; /// - /// UserId. /// Default: /// public static string UserId { get; set; } = ClaimTypes.NameIdentifier; /// - /// UserId. /// Default: /// public static string Role { get; set; } = ClaimTypes.Role; /// - /// UserId. /// Default: /// public static string Email { get; set; } = ClaimTypes.Email; + + /// + /// Default: "email_verified". + /// + public static string EmailVerified { get; set; } = "email_verified"; + + /// + /// Default: "phone_number". + /// + public static string PhoneNumber { get; set; } = "phone_number"; + + /// + /// Default: "phone_number_verified". + /// + public static string PhoneNumberVerified { get; set; } = "phone_number_verified"; } } diff --git a/test/Volo.Abp.AspNetCore.Authentication.OAuth.Tests/Volo.Abp.AspNetCore.Authentication.OAuth.Tests.csproj b/test/Volo.Abp.AspNetCore.Authentication.OAuth.Tests/Volo.Abp.AspNetCore.Authentication.OAuth.Tests.csproj new file mode 100644 index 0000000000..e8441e1690 --- /dev/null +++ b/test/Volo.Abp.AspNetCore.Authentication.OAuth.Tests/Volo.Abp.AspNetCore.Authentication.OAuth.Tests.csproj @@ -0,0 +1,24 @@ + + + + netcoreapp2.0 + latest + Volo.Abp.AspNetCore.Authentication.OAuth.Tests + Volo.Abp.AspNetCore.Authentication.OAuth.Tests + true + false + false + false + + + + + + + + + + + + + diff --git a/test/Volo.Abp.AspNetCore.Authentication.OAuth.Tests/Volo/Abp/AspNetCore/Authentication/OAuth/Claims/Class1.cs b/test/Volo.Abp.AspNetCore.Authentication.OAuth.Tests/Volo/Abp/AspNetCore/Authentication/OAuth/Claims/Class1.cs new file mode 100644 index 0000000000..fac9e33e29 --- /dev/null +++ b/test/Volo.Abp.AspNetCore.Authentication.OAuth.Tests/Volo/Abp/AspNetCore/Authentication/OAuth/Claims/Class1.cs @@ -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"); + } + } +} diff --git a/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs index 7763a85355..9b284263bf 100644 --- a/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs +++ b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcTestModule.cs @@ -20,14 +20,6 @@ namespace Volo.Abp.AspNetCore.Mvc )] public class AbpAspNetCoreMvcTestModule : AbpModule { - public override void PreConfigureServices(IServiceCollection services) - { - services.PreConfigure(builder => - { - builder.AddViewLocalization(); - }); - } - public override void ConfigureServices(IServiceCollection services) { services.AddLocalization(); //TODO: Move to the framework..?