diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json index bb6c1aeef4..dfe9e530de 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json @@ -348,15 +348,39 @@ "CompanySize": "Company size", "DetailTrialLicense": "Details", "Requested": "Requested", + "Pending": "Pending", + "Running": "Running", "Activated": "Activated", "PurchasedToNormalLicense": "Purchased", "Expired": "Expired", "TrialLicenseDeletionWarningMessage": "Are you sure you want to delete the trial license? Trial license, organization, support accounts will be deleted!", "LicenseCategoryFilter": "License category", "Permission:SendWelcomeEmail": "Send Welcome Email", + "Permission:ProvisionExistingOrganizationsAi": "Provision Existing Organizations AI", "SendWelcomeEmail": "Send Welcome Email", "SendWelcomeEmailWarningMessage": "Are you sure you want to send welcome email to the organization members?", "SendWelcomeEmailSuccessMessage": "Welcome email sent successfully!", + "ProvisionExistingOrganizationsAi": "Provision Existing Organizations AI", + "ProvisionExistingOrganizationsAiConfirmation": "This will enable AI assisted development for all active organizations, grant included AI credits, and provision provider keys in the background. Do you want to continue?", + "DeleteExistingOrganizationsAiCredentials": "Delete Existing Organizations AI Keys", + "DeleteExistingOrganizationsAiCredentialsConfirmation": "This will revoke existing OpenRouter keys referenced by organizations and remove stored AI credentials from the database so provisioning can be retried. Do you want to continue?", + "ExistingOrganizationsAiOperationAlreadyRunning": "Another existing organizations AI operation is already running.", + "ExistingOrganizationsAiBackfillAlreadyRunning": "An existing organizations AI provisioning job is already running.", + "ExistingOrganizationsAiBackfillMissingManagementApiKey": "OpenRouter management API key is not configured for the admin application. Configure AiAssistedDevelopment:Providers:OpenRouter:ManagementApiKey before starting this operation.", + "NoActiveOrganizationsFoundForAiBackfill": "No active organizations were found for AI provisioning.", + "NoOrganizationsFoundForAiCredentialCleanup": "No organizations with AI credentials were found for cleanup.", + "ExistingOrganizationsAiBackfillNotFound": "The existing organizations AI provisioning operation was not found.", + "ExistingOrganizationsAiBackfillCompleted": "Existing organizations AI provisioning completed successfully.", + "ExistingOrganizationsAiBackfillFailed": "Existing organizations AI provisioning failed.", + "ExistingOrganizationsAiCredentialCleanupCompleted": "Existing organizations AI credential cleanup completed successfully.", + "ExistingOrganizationsAiCredentialCleanupFailed": "Existing organizations AI credential cleanup failed.", + "CurrentOrganization": "Current organization", + "Processed": "Processed", + "Succeeded": "Succeeded", + "Failed": "Failed", + "Cancelled": "Cancelled", + "CompletedAt": "Completed at", + "LastError": "Last error", "Activate": "Activate", "ActivateTrialLicenseWarningMessage": " When you activate a trial license, a welcome e-mail will be sent to the user. Do you want to activate it?", "ActivateTrialLicenseSuccessMessage": "Activated successfully and the welcome e-mail sent to the organization members.", diff --git a/common.props b/common.props index 7e73e27096..368b7f88a8 100644 --- a/common.props +++ b/common.props @@ -1,8 +1,8 @@ latest - 10.4.0-rc.2 - 5.4.0-rc.2 + 10.5.0-preview + 5.5.0-preview $(NoWarn);CS1591;CS0436 https://abp.io/assets/abp_nupkg.png https://abp.io/ diff --git a/docs/en/modules/openiddict.md b/docs/en/modules/openiddict.md index c1ed40ff9f..910bb2c738 100644 --- a/docs/en/modules/openiddict.md +++ b/docs/en/modules/openiddict.md @@ -303,6 +303,18 @@ PreConfigure(options => - `UpdateAbpClaimTypes(default: true)`: Updates `AbpClaimTypes` to be compatible with the Openiddict claims. - `AddDevelopmentEncryptionAndSigningCertificate(default: true)`: Registers (and generates if necessary) a user-specific development encryption/development signing certificate. This is a certificate used for signing and encrypting the tokens and for **development environment only**. You must set it to **false** for non-development environments. +- `UseDefaultScopesForClientCredentials(default: false)`: When set to `true`, the access token issued for the `client_credentials` grant automatically grants the scopes configured on the client application (permissions prefixed with `oi_scp:`) when the client does not explicitly request any scope. +- `UseDefaultScopesForPassword(default: false)`: When set to `true`, the token response for the `password` grant automatically grants the scopes configured on the client application when the client does not explicitly request any scope. If the configured scopes include `openid`/`profile`/`email`/`roles`, the corresponding `id_token` and claim destinations are affected as well. +- `UseDefaultScopesForTokenExchange(default: false)`: When set to `true`, the token response for the `urn:ietf:params:oauth:grant-type:token-exchange` grant automatically grants the scopes configured on the client application when the client does not explicitly request any scope. If the configured scopes include `openid`/`profile`/`email`/`roles`, the corresponding `id_token` and claim destinations are affected as well. + +Example to enable the default-scope fallback for the `client_credentials` grant: + +```csharp +PreConfigure(options => +{ + options.UseDefaultScopesForClientCredentials = true; +}); +``` > `AddDevelopmentEncryptionAndSigningCertificate` cannot be used in applications deployed on IIS or Azure App Service: trying to use them on IIS or Azure App Service will result in an exception being thrown at runtime (unless the application pool is configured to load a user profile). To avoid that, consider creating self-signed certificates and storing them in the X.509 certificates store of the host machine(s). Please refer to: https://documentation.openiddict.com/configuration/encryption-and-signing-credentials.html#registering-a-development-certificate diff --git a/modules/openiddict/app/OpenIddict.Demo.Server/OpenIddict.Demo.Server.csproj b/modules/openiddict/app/OpenIddict.Demo.Server/OpenIddict.Demo.Server.csproj index 09a185919a..2bc17b4f7c 100644 --- a/modules/openiddict/app/OpenIddict.Demo.Server/OpenIddict.Demo.Server.csproj +++ b/modules/openiddict/app/OpenIddict.Demo.Server/OpenIddict.Demo.Server.csproj @@ -68,6 +68,10 @@ runtime; build; native; contentfiles; analyzers compile; contentFiles; build; buildMultitargeting; buildTransitive; analyzers; native + + runtime; build; native; contentfiles; analyzers + compile; contentFiles; build; buildMultitargeting; buildTransitive; analyzers; native + diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs index 3a9c8109fc..48496874d0 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictAspNetCoreModule.cs @@ -25,9 +25,16 @@ public class AbpOpenIddictAspNetCoreModule : AbpModule Configure(options => { + options.ClaimsPrincipalHandlers.Add(); options.ClaimsPrincipalHandlers.Add(); }); + var preActions = context.Services.GetPreConfigureActions(); + Configure(options => + { + preActions.Configure(options); + }); + Configure(options => { options.ViewLocationFormats.Add("/Volo/Abp/OpenIddict/Views/{1}/{0}.cshtml"); diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictOptions.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictOptions.cs index 3339b6d376..f0c2415fb2 100644 --- a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictOptions.cs +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/AbpOpenIddictOptions.cs @@ -25,4 +25,33 @@ public class AbpOpenIddictAspNetCoreOptions /// Set the url of the select account page. /// public string SelectAccountPage { get; set; } = "~/Account/SelectAccount"; + + /// + /// When set to true, the access token issued for the client_credentials grant + /// automatically includes the scopes configured on the client application (permissions + /// prefixed with oi_scp:) when the client does not explicitly request any scope. + /// Default: false. + /// + public bool UseDefaultScopesForClientCredentials { get; set; } + + /// + /// When set to true, the token response for the password grant automatically + /// grants the scopes configured on the client application (permissions prefixed with + /// oi_scp:) when the client does not explicitly request any scope. If the configured + /// scopes include openid/profile/email/roles, the corresponding + /// id_token and claim destinations are affected as well. + /// Default: false. + /// + public bool UseDefaultScopesForPassword { get; set; } + + /// + /// When set to true, the token response for the + /// urn:ietf:params:oauth:grant-type:token-exchange grant automatically grants the + /// scopes configured on the client application (permissions prefixed with oi_scp:) + /// when the client does not explicitly request any scope. If the configured scopes include + /// openid/profile/email/roles, the corresponding id_token and + /// claim destinations are affected as well. + /// Default: false. + /// + public bool UseDefaultScopesForTokenExchange { get; set; } } diff --git a/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Claims/AbpDefaultScopesHandler.cs b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Claims/AbpDefaultScopesHandler.cs new file mode 100644 index 0000000000..82ab4d3bb2 --- /dev/null +++ b/modules/openiddict/src/Volo.Abp.OpenIddict.AspNetCore/Volo/Abp/OpenIddict/Claims/AbpDefaultScopesHandler.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Immutable; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using OpenIddict.Abstractions; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.OpenIddict; + +public class AbpDefaultScopesHandler : IAbpOpenIddictClaimsPrincipalHandler, ITransientDependency +{ + public ILogger Logger { get; set; } + = NullLogger.Instance; + + public virtual async Task HandleAsync(AbpOpenIddictClaimsPrincipalHandlerContext context) + { + var options = context.ScopeServiceProvider + .GetRequiredService>().Value; + + var request = context.OpenIddictRequest; + if (!IsDefaultScopesEnabled(request, options)) + { + return; + } + + if (!context.Principal.GetScopes().IsDefaultOrEmpty) + { + return; + } + + var clientId = request.ClientId; + if (string.IsNullOrEmpty(clientId)) + { + return; + } + + var applicationManager = context.ScopeServiceProvider.GetRequiredService(); + var scopeManager = context.ScopeServiceProvider.GetRequiredService(); + + var application = await applicationManager.FindByClientIdAsync(clientId); + if (application == null) + { + return; + } + + var permissions = await applicationManager.GetPermissionsAsync(application); + var prefix = OpenIddictConstants.Permissions.Prefixes.Scope; + + var scopes = permissions + .Where(p => p.StartsWith(prefix, StringComparison.Ordinal)) + .Select(p => p[prefix.Length..]) + .ToImmutableArray(); + + if (scopes.IsDefaultOrEmpty) + { + return; + } + + Logger.LogDebug( + "Injecting default scopes for client {ClientId} (grant_type {GrantType}): {Scopes}", + clientId, + request.GrantType, + string.Join(", ", scopes)); + + context.Principal.SetScopes(scopes); + context.Principal.SetResources(await scopeManager.ListResourcesAsync(scopes).ToListAsync()); + } + + protected virtual bool IsDefaultScopesEnabled(OpenIddictRequest request, AbpOpenIddictAspNetCoreOptions options) + { + if (request.IsClientCredentialsGrantType()) + { + return options.UseDefaultScopesForClientCredentials; + } + + if (request.IsPasswordGrantType()) + { + return options.UseDefaultScopesForPassword; + } + + if (request.IsTokenExchangeGrantType()) + { + return options.UseDefaultScopesForTokenExchange; + } + + return false; + } +}