From b837648472c7aaceb5966793e35300ea82af371a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 16 May 2022 12:14:15 +0300 Subject: [PATCH] Revisited OpenIddictDataSeedContributor --- .../appsettings.json | 15 +- .../OpenIddictDataSeedContributor.cs | 422 +++++++++++++----- .../MyProjectNameHttpApiHostModule.cs | 2 +- .../appsettings.json | 4 +- 4 files changed, 322 insertions(+), 121 deletions(-) diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/appsettings.json b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/appsettings.json index 7bd454bab3..b3c3ee52e0 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/appsettings.json +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.DbMigrator/appsettings.json @@ -2,22 +2,21 @@ "ConnectionStrings": { "Default": "Server=(LocalDb)\\MSSQLLocalDB;Database=MyProjectName;Trusted_Connection=True" }, - "IdentityServer": { - "Clients": { + "OpenIddict": { + "Applications": { "MyProjectName_Web": { "ClientId": "MyProjectName_Web", - "ClientSecret": "1q2w3e*", - "RootUrl": "https://localhost:44302" + "RootUrl": "https://localhost:44302/" + }, + "MyProjectName_Blazor": { + "ClientId": "MyProjectName_Blazor", + "RootUrl": "https://localhost:44307/" }, "MyProjectName_App": { "ClientId": "MyProjectName_App", "ClientSecret": "1q2w3e*", "RootUrl": "http://localhost:4200" }, - "MyProjectName_Blazor": { - "ClientId": "MyProjectName_Blazor", - "RootUrl": "https://localhost:44307" - }, "MyProjectName_BlazorServerTiered": { "ClientId": "MyProjectName_BlazorServerTiered", "ClientSecret": "1q2w3e*", diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/OpenIddict/OpenIddictDataSeedContributor.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/OpenIddict/OpenIddictDataSeedContributor.cs index 653b27abef..919fd136fa 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/OpenIddict/OpenIddictDataSeedContributor.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Domain/OpenIddict/OpenIddictDataSeedContributor.cs @@ -1,151 +1,353 @@ using System; -using System.Globalization; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Localization; using OpenIddict.Abstractions; +using Volo.Abp; +using Volo.Abp.Authorization.Permissions; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; -using Volo.Abp.MultiTenancy; +using Volo.Abp.PermissionManagement; +using Volo.Abp.Uow; namespace MyCompanyName.MyProjectName.OpenIddict; +/* Creates initial data that is needed to property run the application + * and make client-to-server communication possible. + */ public class OpenIddictDataSeedContributor : IDataSeedContributor, ITransientDependency { - private readonly ICurrentTenant _currentTenant; + private readonly IConfiguration _configuration; private readonly IOpenIddictApplicationManager _applicationManager; private readonly IOpenIddictScopeManager _scopeManager; + private readonly IPermissionDataSeeder _permissionDataSeeder; + private readonly IStringLocalizer L; public OpenIddictDataSeedContributor( - ICurrentTenant currentTenant, - IOpenIddictApplicationManager applicationManager, - IOpenIddictScopeManager scopeManager) + IConfiguration configuration, + IOpenIddictApplicationManager applicationManager, + IOpenIddictScopeManager scopeManager, + IPermissionDataSeeder permissionDataSeeder, + IStringLocalizer l) { - _currentTenant = currentTenant; + _configuration = configuration; _applicationManager = applicationManager; _scopeManager = scopeManager; + _permissionDataSeeder = permissionDataSeeder; + L = l; } - public async Task SeedAsync(DataSeedContext context) + [UnitOfWork] + public virtual async Task SeedAsync(DataSeedContext context) { - if (await _scopeManager.FindByNameAsync("AbpAPI") == null) + await CreateScopesAsync(); + await CreateApplicationsAsync(); + } + + private async Task CreateScopesAsync() + { + if (await _scopeManager.FindByNameAsync("MyProjectName") == null) { - await _scopeManager.CreateAsync(new OpenIddictScopeDescriptor() + await _scopeManager.CreateAsync(new OpenIddictScopeDescriptor { - Name = "AbpAPI", - DisplayName = "Abp API access", - DisplayNames = - { - [CultureInfo.GetCultureInfo("zh-Hans")] = "演示 API 访问", - [CultureInfo.GetCultureInfo("tr")] = "API erişimi" - }, + Name = "MyProjectName", + DisplayName = "MyProjectName API", Resources = { - "AbpAPIResource" + "MyProjectName" } }); } + } + + private async Task CreateApplicationsAsync() + { + var commonScopes = new List + { + OpenIddictConstants.Permissions.Scopes.Address, + OpenIddictConstants.Permissions.Scopes.Email, + OpenIddictConstants.Permissions.Scopes.Phone, + OpenIddictConstants.Permissions.Scopes.Profile, + OpenIddictConstants.Permissions.Scopes.Roles, + "MyProjectName" + }; + + var configurationSection = _configuration.GetSection("OpenIddict:Applications"); - if (await _applicationManager.FindByClientIdAsync("AbpApp") == null) + //Web Client + var webClientId = configurationSection["MyProjectName_Web:ClientId"]; + if (!webClientId.IsNullOrWhiteSpace()) { - await _applicationManager.CreateAsync(new OpenIddictApplicationDescriptor - { - ClientId = "AbpApp", - ClientSecret = "1q2w3e*", - ConsentType = OpenIddictConstants.ConsentTypes.Explicit, - DisplayName = "Abp Application", - PostLogoutRedirectUris = - { - new Uri("https://localhost:44302/signout-callback-oidc"), - new Uri("http://localhost:4200") + var webClientRootUrl = configurationSection["MyProjectName_Web:RootUrl"].EnsureEndsWith('/'); + + /* MyProjectName_Web client is only needed if you created a tiered + * solution. Otherwise, you can delete this client. */ + await CreateApplicationAsync( + name: webClientId, + type: OpenIddictConstants.ClientTypes.Public, + consentType: OpenIddictConstants.ConsentTypes.Explicit, + displayName: "Web Application", + secret: null, + grantTypes: new List //Hybrid flow + { + OpenIddictConstants.GrantTypes.AuthorizationCode, + OpenIddictConstants.GrantTypes.Implicit }, - RedirectUris = + scopes: commonScopes, + redirectUri: $"{webClientRootUrl}signin-oidc", + postLogoutRedirectUri: $"{webClientRootUrl}signout-callback-oidc" + ); + } + + //Console Test / Angular Client + var consoleAndAngularClientId = configurationSection["MyProjectName_App:ClientId"]; + if (!consoleAndAngularClientId.IsNullOrWhiteSpace()) + { + var webClientRootUrl = configurationSection["MyProjectName_App:RootUrl"]?.TrimEnd('/'); + await CreateApplicationAsync( + name: consoleAndAngularClientId, + type: OpenIddictConstants.ClientTypes.Confidential, + consentType: OpenIddictConstants.ConsentTypes.Explicit, + displayName: "Console Test / Angular Application", + secret: configurationSection["MyProjectName_App:ClientSecret"] ?? "1q2w3e*", + grantTypes: new List { - new Uri("https://localhost:44302/signin-oidc"), - new Uri("http://localhost:4200") + OpenIddictConstants.GrantTypes.AuthorizationCode, + OpenIddictConstants.GrantTypes.Password, + OpenIddictConstants.GrantTypes.ClientCredentials, + "LinkLogin", + "Impersonation" }, - Permissions = - { - OpenIddictConstants.Permissions.Endpoints.Authorization, - OpenIddictConstants.Permissions.Endpoints.Token, - OpenIddictConstants.Permissions.Endpoints.Device, - OpenIddictConstants.Permissions.Endpoints.Introspection, - OpenIddictConstants.Permissions.Endpoints.Revocation, - OpenIddictConstants.Permissions.Endpoints.Logout, - - OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, - OpenIddictConstants.Permissions.GrantTypes.Implicit, - OpenIddictConstants.Permissions.GrantTypes.Password, - OpenIddictConstants.Permissions.GrantTypes.RefreshToken, - OpenIddictConstants.Permissions.GrantTypes.DeviceCode, - OpenIddictConstants.Permissions.GrantTypes.ClientCredentials, - - OpenIddictConstants.Permissions.ResponseTypes.Code, - OpenIddictConstants.Permissions.ResponseTypes.CodeIdToken, - OpenIddictConstants.Permissions.ResponseTypes.CodeIdTokenToken, - OpenIddictConstants.Permissions.ResponseTypes.CodeToken, - OpenIddictConstants.Permissions.ResponseTypes.IdToken, - OpenIddictConstants.Permissions.ResponseTypes.IdTokenToken, - OpenIddictConstants.Permissions.ResponseTypes.None, - OpenIddictConstants.Permissions.ResponseTypes.Token, - - OpenIddictConstants.Permissions.Scopes.Roles, - OpenIddictConstants.Permissions.Scopes.Profile, - OpenIddictConstants.Permissions.Scopes.Email, - OpenIddictConstants.Permissions.Scopes.Address, - OpenIddictConstants.Permissions.Scopes.Phone, - OpenIddictConstants.Permissions.Prefixes.Scope + "AbpAPI" - } - }); + scopes: commonScopes, + redirectUri: webClientRootUrl, + postLogoutRedirectUri: webClientRootUrl + ); } - - if (await _applicationManager.FindByClientIdAsync("AbpBlazorWASMApp") == null) + + // Blazor Client + var blazorClientId = configurationSection["MyProjectName_Blazor:ClientId"]; + if (!blazorClientId.IsNullOrWhiteSpace()) { - await _applicationManager.CreateAsync(new OpenIddictApplicationDescriptor - { - ClientId = "AbpBlazorWASMApp", - ConsentType = OpenIddictConstants.ConsentTypes.Explicit, - DisplayName = "Abp Blazor WASM Application", - PostLogoutRedirectUris = + var blazorRootUrl = configurationSection["MyProjectName_Blazor:RootUrl"].TrimEnd('/'); + + await CreateApplicationAsync( + name: blazorClientId, + type: OpenIddictConstants.ClientTypes.Public, + consentType: OpenIddictConstants.ConsentTypes.Explicit, + displayName: "Blazor Application", + secret: null, + grantTypes: new List { - new Uri("https://localhost:44304/authentication/logout-callback") + OpenIddictConstants.GrantTypes.AuthorizationCode, }, - RedirectUris = + scopes: commonScopes, + redirectUri: $"{blazorRootUrl}/authentication/login-callback", + postLogoutRedirectUri: $"{blazorRootUrl}/authentication/logout-callback" + ); + } + + // Swagger Client + var swaggerClientId = configurationSection["MyProjectName_Swagger:ClientId"]; + if (!swaggerClientId.IsNullOrWhiteSpace()) + { + var swaggerRootUrl = configurationSection["MyProjectName_Swagger:RootUrl"].TrimEnd('/'); + + await CreateApplicationAsync( + name: swaggerClientId, + type: OpenIddictConstants.ClientTypes.Confidential, + consentType: OpenIddictConstants.ConsentTypes.Explicit, + displayName: "Blazor Application", + secret: configurationSection["MyProjectName_Swagger:ClientSecret"], + grantTypes: new List { - new Uri("https://localhost:44304/authentication/login-callback") + OpenIddictConstants.GrantTypes.AuthorizationCode, }, - Permissions = - { - OpenIddictConstants.Permissions.Endpoints.Authorization, - OpenIddictConstants.Permissions.Endpoints.Token, - OpenIddictConstants.Permissions.Endpoints.Device, - OpenIddictConstants.Permissions.Endpoints.Introspection, - OpenIddictConstants.Permissions.Endpoints.Revocation, - OpenIddictConstants.Permissions.Endpoints.Logout, - - OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode, - OpenIddictConstants.Permissions.GrantTypes.Implicit, - OpenIddictConstants.Permissions.GrantTypes.Password, - OpenIddictConstants.Permissions.GrantTypes.RefreshToken, - OpenIddictConstants.Permissions.GrantTypes.DeviceCode, - OpenIddictConstants.Permissions.GrantTypes.ClientCredentials, - - OpenIddictConstants.Permissions.ResponseTypes.Code, - OpenIddictConstants.Permissions.ResponseTypes.CodeIdToken, - OpenIddictConstants.Permissions.ResponseTypes.CodeIdTokenToken, - OpenIddictConstants.Permissions.ResponseTypes.CodeToken, - OpenIddictConstants.Permissions.ResponseTypes.IdToken, - OpenIddictConstants.Permissions.ResponseTypes.IdTokenToken, - OpenIddictConstants.Permissions.ResponseTypes.None, - OpenIddictConstants.Permissions.ResponseTypes.Token, - - OpenIddictConstants.Permissions.Scopes.Roles, - OpenIddictConstants.Permissions.Scopes.Profile, - OpenIddictConstants.Permissions.Scopes.Email, - OpenIddictConstants.Permissions.Scopes.Address, - OpenIddictConstants.Permissions.Scopes.Phone, - - OpenIddictConstants.Permissions.Prefixes.Scope + "AbpAPI" + scopes: commonScopes, + redirectUri: $"{swaggerRootUrl}/swagger/oauth2-redirect.html" + ); + } + } + + private async Task CreateApplicationAsync( + [NotNull] string name, + [NotNull] string type, + [NotNull] string consentType, + string displayName, + string secret, + List grantTypes, + List scopes, + string redirectUri = null, + string postLogoutRedirectUri = null, + List permissions = null) + { + if (!string.IsNullOrEmpty(secret) && string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase)) + { + throw new BusinessException(L["NoClientSecretCanBeSetForPublicApplications"]); + } + + if (string.IsNullOrEmpty(secret) && string.Equals(type, OpenIddictConstants.ClientTypes.Confidential, StringComparison.OrdinalIgnoreCase)) + { + throw new BusinessException(L["TheClientSecretIsRequiredForConfidentialApplications"]); + } + + if (!string.IsNullOrEmpty(name) && await _applicationManager.FindByClientIdAsync(name) != null) + { + return; + //throw new BusinessException(L["TheClientIdentifierIsAlreadyTakenByAnotherApplication"]); + } + + var client = await _applicationManager.FindByClientIdAsync(name); + if (client == null) + { + var application = new OpenIddictApplicationDescriptor + { + ClientId = name, + Type = type, + ClientSecret = secret, + ConsentType = consentType, + DisplayName = displayName + }; + + Check.NotNullOrEmpty(grantTypes, nameof(grantTypes)); + Check.NotNullOrEmpty(scopes, nameof(scopes)); + + if (new [] { OpenIddictConstants.GrantTypes.AuthorizationCode, OpenIddictConstants.GrantTypes.Implicit }.All(grantTypes.Contains)) + { + application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.CodeIdToken); + + if (string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase)) + { + application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.CodeIdTokenToken); + application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.CodeToken); } - }); + } + + if (!redirectUri.IsNullOrWhiteSpace() || !postLogoutRedirectUri.IsNullOrWhiteSpace()) + { + application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Logout); + } + + foreach (var grantType in grantTypes) + { + if (grantType == OpenIddictConstants.GrantTypes.AuthorizationCode) + { + application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode); + application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.Code); + } + + if (grantType == OpenIddictConstants.GrantTypes.AuthorizationCode || grantType == OpenIddictConstants.GrantTypes.Implicit) + { + application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Authorization); + } + + if (grantType == OpenIddictConstants.GrantTypes.AuthorizationCode || + grantType == OpenIddictConstants.GrantTypes.ClientCredentials || + grantType == OpenIddictConstants.GrantTypes.Password || + grantType == OpenIddictConstants.GrantTypes.RefreshToken || + grantType == OpenIddictConstants.GrantTypes.DeviceCode) + { + application.Permissions.Add(OpenIddictConstants.Permissions.Endpoints.Token); + } + + if (grantType == OpenIddictConstants.GrantTypes.ClientCredentials) + { + application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.ClientCredentials); + } + + if (grantType == OpenIddictConstants.GrantTypes.Implicit) + { + application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.Implicit); + } + + if (grantType == OpenIddictConstants.GrantTypes.Password) + { + application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.Password); + } + + if (grantType == OpenIddictConstants.GrantTypes.RefreshToken) + { + application.Permissions.Add(OpenIddictConstants.Permissions.GrantTypes.RefreshToken); + } + + if (grantType == OpenIddictConstants.GrantTypes.Implicit) + { + application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.IdToken); + if (string.Equals(type, OpenIddictConstants.ClientTypes.Public, StringComparison.OrdinalIgnoreCase)) + { + application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.IdTokenToken); + application.Permissions.Add(OpenIddictConstants.Permissions.ResponseTypes.Token); + } + } + } + + var buildInScopes = new [] + { + OpenIddictConstants.Permissions.Scopes.Address, + OpenIddictConstants.Permissions.Scopes.Email, + OpenIddictConstants.Permissions.Scopes.Phone, + OpenIddictConstants.Permissions.Scopes.Profile, + OpenIddictConstants.Permissions.Scopes.Roles + }; + + foreach (var scope in scopes) + { + if (buildInScopes.Contains(scope)) + { + application.Permissions.Add(scope); + } + else + { + application.Permissions.Add(OpenIddictConstants.Permissions.Prefixes.Scope + scope); + } + } + + if (redirectUri != null) + { + if (!redirectUri.IsNullOrEmpty()) + { + if (!Uri.TryCreate(redirectUri, UriKind.Absolute, out var uri) || !uri.IsWellFormedOriginalString()) + { + throw new BusinessException(L["InvalidRedirectUri", redirectUri]); + } + + if (application.RedirectUris.All(x => x != uri)) + { + application.RedirectUris.Add(uri); + } + } + } + + if (postLogoutRedirectUri != null) + { + if (!postLogoutRedirectUri.IsNullOrEmpty()) + { + if (!Uri.TryCreate(postLogoutRedirectUri, UriKind.Absolute, out var uri) || !uri.IsWellFormedOriginalString()) + { + throw new BusinessException(L["InvalidPostLogoutRedirectUri", postLogoutRedirectUri]); + } + + if (application.PostLogoutRedirectUris.All(x => x != uri)) + { + application.PostLogoutRedirectUris.Add(uri); + } + } + } + + if (permissions != null) + { + await _permissionDataSeeder.SeedAsync( + ClientPermissionValueProvider.ProviderName, + name, + permissions, + null + ); + } + + await _applicationManager.CreateAsync(application); } } -} + +} \ No newline at end of file diff --git a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs index 45fccf2c90..763856f427 100644 --- a/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs +++ b/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.HttpApi.Host/MyProjectNameHttpApiHostModule.cs @@ -102,7 +102,7 @@ public class MyProjectNameHttpApiHostModule : AbpModule { options.Authority = configuration["AuthServer:Authority"]; options.RequireHttpsMetadata = Convert.ToBoolean(configuration["AuthServer:RequireHttpsMetadata"]); - options.Audience = "AbpAPIResource"; + options.Audience = "MyProjectName"; // See OpenIddictServerModule`s PreConfigureServices method. options.TokenValidationParameters.IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Abp_OpenIddict_Demo_C40DBB176E78")); diff --git a/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/appsettings.json b/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/appsettings.json index 8a299f9f2b..9214292fa9 100644 --- a/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/appsettings.json +++ b/templates/app/aspnet-core/test/MyCompanyName.MyProjectName.HttpApi.Client.ConsoleTestApp/appsettings.json @@ -7,12 +7,12 @@ "IdentityClients": { "Default": { "GrantType": "password", - "ClientId": "AbpApp", + "ClientId": "MyProjectName_App", "ClientSecret": "1q2w3e*", "UserName": "admin", "UserPassword": "1q2w3E*", "Authority": "https://localhost:44301", - "Scope": "AbpAPI profile roles email phone offline_access" + "Scope": "MyProjectName" } } } \ No newline at end of file