diff --git a/.deployment/module/cloudDeploy.json b/.deployment/module/cloudDeploy.json index 11081d4..151703d 100644 --- a/.deployment/module/cloudDeploy.json +++ b/.deployment/module/cloudDeploy.json @@ -1,5 +1,5 @@ { - "artifactKey": "VirtoCommerce.Customer", + "artifactKey": "VirtoCommerce.OpenIdConnectModule", "deployRepo": "vc-deploy-dev", "cmPath": "backend/packages.json", "dev": { diff --git a/.editorconfig b/.editorconfig index 52a5b14..d467f1b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -102,7 +102,7 @@ csharp_preserve_single_line_statements = false csharp_preserve_single_line_blocks = true csharp_using_directive_placement = outside_namespace:silent csharp_prefer_simple_using_statement = true:suggestion -csharp_style_namespace_declarations = block_scoped:silent +csharp_style_namespace_declarations = file_scoped:silent csharp_style_prefer_method_group_conversion = true:silent csharp_style_prefer_top_level_statements = true:silent csharp_style_expression_bodied_lambdas = true:silent diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f9421a3..22e3547 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,8 +1,8 @@ # Common settings -.github/* @mvktsk @Vectorfield4 -.gitignore @mvktsk @Vectorfield4 -.dockerignore @mvktsk @Vectorfield4 +.github/* @VirtoCommerce/platform +.gitignore @VirtoCommerce/platform +.dockerignore @VirtoCommerce/platform # Main Code and Tests @@ -13,4 +13,4 @@ tests/* @VirtoCommerce/platform VirtoCommerce.Platform.sln @VirtoCommerce/platform # Docs -docs/* @zashchitnik-kuka +docs/* @VirtoCommerce/platform diff --git a/.github/workflows/module-ci.yml b/.github/workflows/module-ci.yml index 1634061..d7b89cd 100644 --- a/.github/workflows/module-ci.yml +++ b/.github/workflows/module-ci.yml @@ -1,5 +1,5 @@ -# v3.800.6 -# https://virtocommerce.atlassian.net/browse/VCST-915 +# v3.800.10 +# https://virtocommerce.atlassian.net/browse/VCST-1738 name: Module CI on: @@ -239,9 +239,9 @@ jobs: module-katalon-tests: if: ${{ ((github.ref == 'refs/heads/dev') && (github.event_name == 'push') && (needs.ci.outputs.run-e2e == 'true')) || - (github.event_name == 'workflow_dispatch')}} + (github.event_name == 'workflow_dispatch') || (github.base_ref == 'dev') && (github.event_name == 'pull_request') }} needs: 'ci' - uses: VirtoCommerce/.github/.github/workflows/e2e.yml@v3.800.6 + uses: VirtoCommerce/.github/.github/workflows/e2e.yml@v3.800.10 with: katalonRepo: 'VirtoCommerce/vc-quality-gate-katalon' @@ -260,7 +260,7 @@ jobs: deploy-cloud: if: ${{ (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev') && github.event_name == 'push' }} needs: ci - uses: VirtoCommerce/.github/.github/workflows/deploy-cloud.yml@v3.800.6 + uses: VirtoCommerce/.github/.github/workflows/deploy-cloud.yml@v3.800.10 with: releaseSource: module moduleId: ${{ needs.ci.outputs.moduleId }} diff --git a/.github/workflows/module-release-hotfix.yml b/.github/workflows/module-release-hotfix.yml index 42ed2a1..b5a3dae 100644 --- a/.github/workflows/module-release-hotfix.yml +++ b/.github/workflows/module-release-hotfix.yml @@ -1,5 +1,5 @@ -# v3.800.6 -# https://virtocommerce.atlassian.net/browse/VCST-915 +# v3.800.10 +# https://virtocommerce.atlassian.net/browse/VCST-1738 name: Release hotfix on: @@ -13,12 +13,12 @@ on: jobs: test: - uses: VirtoCommerce/.github/.github/workflows/test-and-sonar.yml@v3.800.6 + uses: VirtoCommerce/.github/.github/workflows/test-and-sonar.yml@v3.800.10 secrets: sonarToken: ${{ secrets.SONAR_TOKEN }} build: - uses: VirtoCommerce/.github/.github/workflows/build.yml@v3.800.6 + uses: VirtoCommerce/.github/.github/workflows/build.yml@v3.800.10 with: uploadPackage: 'true' uploadDocker: 'false' @@ -46,7 +46,7 @@ jobs: publish-github-release: needs: [build, test, get-metadata] - uses: VirtoCommerce/.github/.github/workflows/publish-github.yml@v3.800.6 + uses: VirtoCommerce/.github/.github/workflows/publish-github.yml@v3.800.10 with: fullKey: ${{ needs.build.outputs.packageFullKey }} changeLog: '${{ needs.get-metadata.outputs.changeLog }}' diff --git a/.github/workflows/publish-nugets.yml b/.github/workflows/publish-nugets.yml index c683367..8c52109 100644 --- a/.github/workflows/publish-nugets.yml +++ b/.github/workflows/publish-nugets.yml @@ -1,5 +1,5 @@ -# v3.800.6 -# https://virtocommerce.atlassian.net/browse/VCST-915 +# v3.800.10 +# https://virtocommerce.atlassian.net/browse/VCST-1738 name: Publish nuget on: @@ -13,12 +13,12 @@ on: jobs: test: - uses: VirtoCommerce/.github/.github/workflows/test-and-sonar.yml@v3.800.6 + uses: VirtoCommerce/.github/.github/workflows/test-and-sonar.yml@v3.800.10 secrets: sonarToken: ${{ secrets.SONAR_TOKEN }} build: - uses: VirtoCommerce/.github/.github/workflows/build.yml@v3.800.6 + uses: VirtoCommerce/.github/.github/workflows/build.yml@v3.800.10 with: uploadPackage: 'true' uploadDocker: 'false' @@ -29,7 +29,7 @@ jobs: publish-nuget: needs: [build, test] - uses: VirtoCommerce/.github/.github/workflows/publish-github.yml@v3.800.6 + uses: VirtoCommerce/.github/.github/workflows/publish-github.yml@v3.800.10 with: fullKey: ${{ needs.build.outputs.packageFullKey }} forceGithub: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ee43ae2..17e5d39 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,5 +1,5 @@ -# v3.800.6 -# https://virtocommerce.atlassian.net/browse/VCST-915 +# v3.800.10 +# https://virtocommerce.atlassian.net/browse/VCST-1738 name: Release on: @@ -7,6 +7,6 @@ on: jobs: release: - uses: VirtoCommerce/.github/.github/workflows/release.yml@v3.800.6 + uses: VirtoCommerce/.github/.github/workflows/release.yml@v3.800.10 secrets: envPAT: ${{ secrets.REPO_TOKEN }} \ No newline at end of file diff --git a/README.md b/README.md index 808385b..bfdc000 100644 --- a/README.md +++ b/README.md @@ -11,49 +11,114 @@ OpenID Connect is an identity module on top of the OAuth 2.0 protocol, allowing ## Configuration The module configuration for OpenID Connect (OIDC) authentication is defined in the appsettings.json file under the `oidc` section. This configuration enables the application to authenticate users using the OIDC protocol. Below are the parameters and their descriptions: -* Enabled: A boolean value indicating whether OIDC authentication is enabled. Set to true to enable. -* AuthenticationType: Specifies the type of authentication. For OIDC, this should be set to "oidc". -* Authority: The URL of the OIDC provider. This is the base address of the identity provider, e.g., https://localhost:5001. -* AuthenticationCaption: A user-friendly name for the authentication method, e.g., "OpenID Connect". -* ApplicationId: The unique identifier for the application registered with the OIDC provider. -* ClientId: The client identifier issued to the application by the OIDC provider. -* ClientSecret: The client secret issued to the application by the OIDC provider. This should be kept confidential. -* DefaultUserType: Specifies the default user type upon successful authentication, e.g., "Manager". -* ResponseMode: Defines how the authorization response is returned. Common values are "query" or "fragment". -* ResponseType: Specifies the type of response expected from the OIDC provider. For example, "code" for authorization code flow. -* RequireHttpsMetadata: A boolean value indicating whether HTTPS metadata is required. Set to false for development environments. -* SaveTokens: A boolean value indicating whether to save the tokens received from the OIDC provider. -* UseTokenLifetime: A boolean value indicating whether to use the token’s lifetime as provided by the OIDC provider. -* Scope: An array of strings specifying the scopes requested from the OIDC provider, e.g., ["profile", "email"]. -* GetClaimsFromUserInfoEndpoint: A boolean value indicating whether to retrieve additional claims from the user info endpoint. -* CallbackPath: The path to which the OIDC provider will redirect after authentication, by default "/signin-openid-connect" +* `Enabled`: A boolean value indicating whether OIDC authentication is enabled. Set to `true` to enable. Default value is `false`. +* `AuthenticationType`: Specifies the unique name of the authentication method. Default value is `"oidc"`. +* `AuthenticationCaption`: A user-friendly name for the authentication method. Default value is `"OpenID Connect"`. +* `AllowCreateNewUser`: A boolean value indicating whether a new user should be created upon successful authentication. Default value is `true`. +* `DefaultUserType`: Specifies the user type of a new user. Default value is `"Manager"`. +* `DefaultUserRoles`: Specifies the list of user roles of a new user. Default value is `[]`. +* `UserNameClaimType`: Specifies the claim type used to retrieve the username. Default value is `"name"`. +* `EmailClaimType`: Specifies the claim type used to retrieve the email address. Default value is `"email"`. +* `HasLoginForm`: A boolean value indicating whether to display a dedicated login form or not. Default value is `true`. +* `Priority`: An integer value specifying the sorting order of the authentication method. Default value is `1`. +* `LogoUrl`: URL of the logo for the OpenId Connect authentication provider. +* `Authority`: The URL of the OIDC provider. This is the base address of the identity provider, e.g., https://localhost:5001. +* `ClientId`: The client identifier issued to the application by the OIDC provider. +* `ClientSecret`: The client secret issued to the application by the OIDC provider. This should be kept confidential. +* `Scope`: An array of strings specifying the scopes requested from the OIDC provider. Default value is `["openid", "profile", "email"]`. +* `ResponseMode`: Defines how the authorization response is returned. Default value is `"form_post"`. +* `ResponseType`: Specifies the type of response expected from the OIDC provider. Default value is `"id_token"`. +* `GetClaimsFromUserInfoEndpoint`: A boolean value indicating whether to retrieve additional claims from the user info endpoint. +* `CallbackPath`: The path to which the OIDC provider will redirect after authentication. Default value is `"/signin-oidc"`. +* `SignedOutCallbackPath`: The path to which the OIDC provider will redirect after signing out. Default value is `"/signout-callback-oidc"`. -> Note: If you other external sign-in providers installed (Microsoft Entra ID or Google SSO) you need to make sure to use unique callback paths for each provider. +The list of other parameters can be found in the [OpenIdConnectOptions](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.openidconnectoptions?view=aspnetcore-1.1&viewFallbackFrom=aspnetcore-8.0) documentation. +> [!IMPORTANT] +> If you have other external sign-in providers installed (Microsoft Entra ID or Google SSO) you need to make sure to use unique authentication types and callback paths for each provider. + +> [!NOTE] +> The module was designed and tested with this version of the platform [VCST-1415: Platform as authorization server](https://github.com/VirtoCommerce/vc-platform/pull/2809) + +### Example settings for Virto Commerce ```json "oidc": { "Enabled": true, - "AuthenticationType": "oidc", + "AuthenticationType": "virto", + "AuthenticationCaption": "Virto Commerce", "Authority": "https://localhost:5001", - "AuthenticationCaption": "OpenID Connect", - "ApplicationId": "cf4cb5a0-17c8-4cde-91fd-f23f0891ae20", - "ClientId": "cf4cb5a0-17c8-4cde-91fd-f23f0891ae20", - "ClientSecret": "ad724695-ca42-4271-a9ba-636a2d50f7ec", - "DefaultUserType": "Manager", - "ResponseMode" : "query", - "ResponseType" : "code", - "RequireHttpsMetadata" : false, - "SaveTokens" : true, - "UseTokenLifetime" : true, - "Scope" : ["profile", "email"], - "GetClaimsFromUserInfoEndpoint" : true, - "CallbackPath": "/signin-openid-connect" + "ClientId": "your-client-id", + "ClientSecret": "your-client-secret", + "ResponseMode": "query", + "ResponseType": "code", + "GetClaimsFromUserInfoEndpoint": true + } +``` + +### Example settings for Google +```json + "oidc": { + "Enabled": true, + "AuthenticationType": "google", + "AuthenticationCaption": "Google", + "Authority": "https://accounts.google.com", + "ClientId": "your-client-id", + "ClientSecret": "your-client-secret", + "UserNameClaimType": "email" } ``` -## Known limitation -1. The module was designed and tested with this version of the platform [VCST-1415: Platform as authorization server](https://github.com/VirtoCommerce/vc-platform/pull/2809) -2. Supports ResponseMode query only. +### Example settings for Microsoft +```json + "oidc": { + "Enabled": true, + "AuthenticationType": "microsoft", + "AuthenticationCaption": "Microsoft", + "Authority": "https://login.microsoftonline.com/your-tenant-id/v2.0", + "ClientId": "your-application-id", + "UserNameClaimType": "preferred_username" + } +``` + +### Example settings for multiple configurations +```json + "oidc": [ + { + "Enabled": true, + "AuthenticationType": "virto", + "AuthenticationCaption": "Virto Commerce", + "Authority": "https://localhost:5001", + "ClientId": "your-client-id", + "ClientSecret": "your-client-secret", + "ResponseMode": "query", + "ResponseType": "code", + "GetClaimsFromUserInfoEndpoint": true, + "CallbackPath": "/signin-virto", + "SignedOutCallbackPath": "/signout-virto" + }, + { + "Enabled": true, + "AuthenticationType": "google", + "AuthenticationCaption": "Google", + "Authority": "https://accounts.google.com", + "ClientId": "your-client-id", + "ClientSecret": "your-client-secret", + "UserNameClaimType": "email", + "CallbackPath": "/signin-google", + "SignedOutCallbackPath": "/signout-google" + }, + { + "Enabled": true, + "AuthenticationType": "microsoft", + "AuthenticationCaption": "Microsoft", + "Authority": "https://login.microsoftonline.com/your-tenant-id/v2.0", + "ClientId": "your-application-id", + "UserNameClaimType": "preferred_username", + "CallbackPath": "/signin-microsoft", + "SignedOutCallbackPath": "/signout-microsoft" + } + ] +``` ## License Copyright (c) Virto Solutions LTD. All rights reserved. diff --git a/src/VirtoCommerce.OpenIdConnectModule.Core/Events/.keep b/src/VirtoCommerce.OpenIdConnectModule.Core/Events/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/src/VirtoCommerce.OpenIdConnectModule.Core/Models/OidcOptions.cs b/src/VirtoCommerce.OpenIdConnectModule.Core/Models/OidcOptions.cs index d74c860..758d905 100644 --- a/src/VirtoCommerce.OpenIdConnectModule.Core/Models/OidcOptions.cs +++ b/src/VirtoCommerce.OpenIdConnectModule.Core/Models/OidcOptions.cs @@ -1,133 +1,59 @@ -using System.Collections.Generic; +namespace VirtoCommerce.OpenIdConnectModule.Core.Models; -namespace VirtoCommerce.OpenIdConnectModule.Core.Models +public class OidcOptions { - public class OidcOptions - { - /// - /// Determines whether the user authentication via OpenId Connect is enabled. - /// - public bool Enabled { get; set; } - - /// - /// Sets AuthenticationType value for OpenId Connect authentication provider. - /// - public string AuthenticationType { get; set; } - - /// - /// Sets human-readable caption for OpenId Connect authentication provider. It is visible on sign-in page. - /// - public string AuthenticationCaption { get; set; } - - /// - /// Application ID of the VirtoCommerce platform application registered in OpenId Connect. - /// - public string ApplicationId { get; set; } - - /// - /// URL of the OpenId Connect endpoint used for authentication. - /// - public string Authority { get; set; } - - /// - /// Default user type for users created by OpenId Connect accounts. - /// - public string DefaultUserType { get; set; } - - /// - /// Default user roles for users created by OpenId Connect accounts. - /// - public string[] DefaultUserRoles { get; set; } - - /// - /// Allow creating new user when a user authenticates via IDP for the first time - /// - public bool AllowCreateNewUser { get; set; } = true; - - /// - /// Display dedicated login form or not - /// - public bool HasLoginForm { get; set; } = true; - - /// - /// Gets or sets the discovery endpoint for obtaining metadata - /// - public string MetadataAddress { get; set; } - - /// - /// Client Id - /// - public string ClientId { get; set; } - - /// - /// Client Id - /// - public string ClientSecret { get; set; } - - /// - /// Client Id - /// - public string CallbackPath { get; set; } = "/signin-openid-connect"; - - /// - /// Response type - /// - public string ResponseType { get; set; } - - /// - /// Response mode - /// - public string ResponseMode { get; set; } - - /// - /// Indicates that the authentication session lifetime (e.g. cookies) should match that of the authentication token. - /// If the token does not provide lifetime information then normal session lifetimes will be used. - /// This is disabled by default. - /// - public bool UseTokenLifetime { get; set; } - - /// - /// Defines whether access and refresh tokens should be stored in the AuthenticationProperties after a successful authorization. - /// This property is set to false by default to reduce the size of the final authentication cookie. - /// - public bool SaveTokens { get; set; } - - /// - /// Gets or sets if HTTPS is required for the metadata address or authority. The default is true. - /// This should be disabled only in development environments. - /// - public bool RequireHttpsMetadata { get; set; } - - /// - /// Gets or sets the authentication scheme corresponding to the middleware responsible of persisting user's identity after a successful authentication. - /// This value typically corresponds to a cookie middleware registered in the Startup class. When omitted, SignInScheme is used as a fallback value. - /// - public string SignInScheme { get; set; } - - /// - /// Gets the list of permissions to request. - /// - public List Scope { get; set; } - - /// - /// Boolean to set whether the middleware should go to user info endpoint to retrieve additional claims or not after creating an identity from id_token received from token endpoint. - /// The default is 'false'. - /// - public bool GetClaimsFromUserInfoEndpoint { get; set; } - - /// - /// - /// - public string SignedOutCallbackPath { get; set; } - - /// - /// - /// - public string SignedOutRedirectUri { get; set; } - - /// - /// - /// - public string SignOutScheme { get; set; } - } + /// + /// Determines whether the user authentication via OpenId Connect is enabled. + /// + public bool Enabled { get; set; } = false; + + /// + /// Sets AuthenticationType value for OpenId Connect authentication provider. + /// + public string AuthenticationType { get; set; } = "oidc"; + + /// + /// Sets human-readable caption for OpenId Connect authentication provider. It is visible on sign-in page. + /// + public string AuthenticationCaption { get; set; } = "OpenID Connect"; + + /// + /// Allow creating new user when a user authenticates via IDP for the first time + /// + public bool AllowCreateNewUser { get; set; } = true; + + /// + /// Default user type for users created by OpenId Connect accounts. + /// + public string DefaultUserType { get; set; } = "Manager"; + + /// + /// Default user roles for users created by OpenId Connect accounts. + /// + public string[] DefaultUserRoles { get; set; } = []; + + /// + /// Specifies the claim type used to retrieve the username. + /// + public string UserNameClaimType { get; set; } = "name"; + + /// + /// Specifies the claim type used to retrieve the email address. + /// + public string EmailClaimType { get; set; } = "email"; + + /// + /// Display dedicated login form or not + /// + public bool HasLoginForm { get; set; } = true; + + /// + /// The sorting order of the external sign-in provider. + /// + public int Priority { get; set; } = 1; + + /// + /// URL of the logo for the OpenId Connect authentication provider. + /// + public string LogoUrl { get; set; } = "Modules/$(VirtoCommerce.OpenIdConnectModule)/Content/openid-icon.webp"; } diff --git a/src/VirtoCommerce.OpenIdConnectModule.Core/ModuleConstants.cs b/src/VirtoCommerce.OpenIdConnectModule.Core/ModuleConstants.cs deleted file mode 100644 index d454949..0000000 --- a/src/VirtoCommerce.OpenIdConnectModule.Core/ModuleConstants.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace VirtoCommerce.OpenIdConnectModule.Core; - -public static class ModuleConstants -{ - public const string OidcAuthenticationType = "oidc"; - public const int ProviderPriority = 200; - public const string JsonKeyName = "name"; - public const string JsonKeyEmail = "email"; -} diff --git a/src/VirtoCommerce.OpenIdConnectModule.Core/Notifications/.keep b/src/VirtoCommerce.OpenIdConnectModule.Core/Notifications/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/src/VirtoCommerce.OpenIdConnectModule.Core/Services/.keep b/src/VirtoCommerce.OpenIdConnectModule.Core/Services/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/src/VirtoCommerce.OpenIdConnectModule.Core/VirtoCommerce.OpenIdConnectModule.Core.csproj b/src/VirtoCommerce.OpenIdConnectModule.Core/VirtoCommerce.OpenIdConnectModule.Core.csproj index c07fcda..559727b 100644 --- a/src/VirtoCommerce.OpenIdConnectModule.Core/VirtoCommerce.OpenIdConnectModule.Core.csproj +++ b/src/VirtoCommerce.OpenIdConnectModule.Core/VirtoCommerce.OpenIdConnectModule.Core.csproj @@ -7,6 +7,6 @@ false - + diff --git a/src/VirtoCommerce.OpenIdConnectModule.Data/Models/.keep b/src/VirtoCommerce.OpenIdConnectModule.Data/Models/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/src/VirtoCommerce.OpenIdConnectModule.Data/Services/OidcExternalSignInProvider.cs b/src/VirtoCommerce.OpenIdConnectModule.Data/Services/OidcExternalSignInProvider.cs index 0ec132c..02920bd 100644 --- a/src/VirtoCommerce.OpenIdConnectModule.Data/Services/OidcExternalSignInProvider.cs +++ b/src/VirtoCommerce.OpenIdConnectModule.Data/Services/OidcExternalSignInProvider.cs @@ -1,42 +1,46 @@ using System.Security.Claims; using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Options; -using VirtoCommerce.OpenIdConnectModule.Core; using VirtoCommerce.OpenIdConnectModule.Core.Models; using VirtoCommerce.Platform.Security.ExternalSignIn; -namespace VirtoCommerce.OpenIdConnectModule.Data.Services +namespace VirtoCommerce.OpenIdConnectModule.Data.Services; + +public class OidcExternalSignInProvider : IExternalSignInProvider { - public class OidcExternalSignInProvider : IExternalSignInProvider + private readonly OidcOptions _oidcOptions; + + public OidcExternalSignInProvider(OidcOptions oidcOptions) { - private readonly OidcOptions _oidcOptions; + _oidcOptions = oidcOptions; + } - public OidcExternalSignInProvider(IOptions oidcOptions) - { - _oidcOptions = oidcOptions.Value; - } + public int Priority => _oidcOptions.Priority; - public int Priority => ModuleConstants.ProviderPriority; + public bool HasLoginForm => _oidcOptions.HasLoginForm; - public bool HasLoginForm => _oidcOptions.HasLoginForm; + public bool AllowCreateNewUser => _oidcOptions.AllowCreateNewUser; - public bool AllowCreateNewUser => _oidcOptions.AllowCreateNewUser; + public string GetUserName(ExternalLoginInfo externalLoginInfo) + { + var userName = externalLoginInfo.Principal.FindFirstValue(_oidcOptions.UserNameClaimType); - public string GetUserName(ExternalLoginInfo externalLoginInfo) - { - var userName = externalLoginInfo.Principal.FindFirstValue(ClaimTypes.Name); + return userName; + } - return userName; - } + public string GetEmail(ExternalLoginInfo externalLoginInfo) + { + var email = externalLoginInfo.Principal.FindFirstValue(_oidcOptions.EmailClaimType); - public string[] GetUserRoles() - { - return _oidcOptions.DefaultUserRoles; - } + return email; + } - public string GetUserType() - { - return _oidcOptions.DefaultUserType; - } + public string[] GetUserRoles() + { + return _oidcOptions.DefaultUserRoles; + } + + public string GetUserType() + { + return _oidcOptions.DefaultUserType; } } diff --git a/src/VirtoCommerce.OpenIdConnectModule.Data/VirtoCommerce.OpenIdConnectModule.Data.csproj b/src/VirtoCommerce.OpenIdConnectModule.Data/VirtoCommerce.OpenIdConnectModule.Data.csproj index e092520..7797f10 100644 --- a/src/VirtoCommerce.OpenIdConnectModule.Data/VirtoCommerce.OpenIdConnectModule.Data.csproj +++ b/src/VirtoCommerce.OpenIdConnectModule.Data/VirtoCommerce.OpenIdConnectModule.Data.csproj @@ -1,4 +1,4 @@ - + net8.0 @@ -7,7 +7,7 @@ false - + diff --git a/src/VirtoCommerce.OpenIdConnectModule.Web/Module.cs b/src/VirtoCommerce.OpenIdConnectModule.Web/Module.cs index 6f72e5e..4af3e76 100644 --- a/src/VirtoCommerce.OpenIdConnectModule.Web/Module.cs +++ b/src/VirtoCommerce.OpenIdConnectModule.Web/Module.cs @@ -1,11 +1,11 @@ +using System; using System.Linq; -using System.Security.Claims; using System.Threading.Tasks; +using System.Web; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using VirtoCommerce.OpenIdConnectModule.Core; using VirtoCommerce.OpenIdConnectModule.Core.Models; using VirtoCommerce.OpenIdConnectModule.Data.Services; using VirtoCommerce.Platform.Core.Modularity; @@ -21,62 +21,83 @@ public class Module : IModule, IHasConfiguration public void Initialize(IServiceCollection serviceCollection) { - Microsoft.IdentityModel.JsonWebTokens.JsonWebTokenHandler.DefaultInboundClaimTypeMap.Clear(); - - var OidcSection = Configuration.GetSection(ModuleConstants.OidcAuthenticationType); - if (OidcSection.GetChildren().Any()) + var oidcSection = Configuration.GetSection("oidc"); + if (!oidcSection.GetChildren().Any()) { - var options = new OidcOptions(); - OidcSection.Bind(options); - serviceCollection.Configure(OidcSection); + return; + } - if (options.Enabled) + // Support both single and multiple configurations + // Single: "oidc": {...} + // Multiple: "oidc": [{...}, {...}] + if (oidcSection.GetSection("0").Exists()) + { + foreach (var section in oidcSection.GetChildren()) { - var authBuilder = new AuthenticationBuilder(serviceCollection); + RegisterOidcProvider(serviceCollection, section); + } + } + else + { + RegisterOidcProvider(serviceCollection, oidcSection); + } + } - authBuilder.AddOpenIdConnect(options.AuthenticationType, options.AuthenticationCaption, - openIdConnectOptions => - { - openIdConnectOptions.ClientId = options.ClientId; - openIdConnectOptions.ClientSecret = options.ClientSecret; - openIdConnectOptions.Authority = options.Authority; - openIdConnectOptions.UseTokenLifetime = options.UseTokenLifetime; - openIdConnectOptions.SaveTokens = options.SaveTokens; - openIdConnectOptions.ResponseMode = options.ResponseMode; - openIdConnectOptions.ResponseType = options.ResponseType; - openIdConnectOptions.MetadataAddress = options.MetadataAddress; - openIdConnectOptions.RequireHttpsMetadata = options.RequireHttpsMetadata; - openIdConnectOptions.SignInScheme = options.SignInScheme; - openIdConnectOptions.SignOutScheme = options.SignOutScheme; - openIdConnectOptions.CallbackPath = options.CallbackPath; - openIdConnectOptions.SignedOutCallbackPath = options.SignedOutCallbackPath; - openIdConnectOptions.SignedOutRedirectUri = options.SignedOutRedirectUri; - openIdConnectOptions.GetClaimsFromUserInfoEndpoint = options.GetClaimsFromUserInfoEndpoint; - options.Scope.ForEach(scope => openIdConnectOptions.Scope.Add(scope)); - - openIdConnectOptions.ClaimActions.MapJsonKey(ClaimTypes.Name, ModuleConstants.JsonKeyName); - openIdConnectOptions.ClaimActions.MapJsonKey(ClaimTypes.Email, ModuleConstants.JsonKeyEmail); - - openIdConnectOptions.Events.OnRedirectToIdentityProvider = context => - { - var oidcUrl = context.Properties.GetOidcUrl(); - if (!string.IsNullOrEmpty(oidcUrl)) - { - context.ProtocolMessage.RedirectUri = oidcUrl; - } - return Task.CompletedTask; - }; - }); - - // register default external provider implementation - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(provider => new ExternalSignInProviderConfiguration + private static void RegisterOidcProvider(IServiceCollection serviceCollection, IConfigurationSection oidcSection) + { + var options = new OidcOptions(); + oidcSection.Bind(options); + + if (options.Enabled) + { + var authBuilder = new AuthenticationBuilder(serviceCollection); + + authBuilder.AddOpenIdConnect(options.AuthenticationType, options.AuthenticationCaption, + openIdConnectOptions => { - AuthenticationType = ModuleConstants.OidcAuthenticationType, - Provider = provider.GetService(), - LogoUrl = "Modules/$(VirtoCommerce.OpenIdConnectModule)/Content/openid-icon.webp" + openIdConnectOptions.MapInboundClaims = false; + + openIdConnectOptions.Scope.Clear(); + if (!oidcSection.GetSection("Scope").Exists()) + { + openIdConnectOptions.Scope.Add("openid"); + openIdConnectOptions.Scope.Add("profile"); + openIdConnectOptions.Scope.Add("email"); + } + + oidcSection.Bind(openIdConnectOptions); + + openIdConnectOptions.Events.OnRedirectToIdentityProvider = context => + { + var oidcUrl = context.Properties.GetOidcUrl(); + if (!string.IsNullOrEmpty(oidcUrl)) + { + context.ProtocolMessage.RedirectUri = oidcUrl; + } + + return Task.CompletedTask; + }; + + openIdConnectOptions.Events.OnAccessDenied = context => + { + // Need a base URI (any) to work with relative URLs + var baseUri = new Uri("https://localhost"); + var uri = new Uri(baseUri, context.ReturnUrl); + var returnUrl = HttpUtility.ParseQueryString(uri.Query).GetValues(context.ReturnUrlParameter)?.FirstOrDefault(); + + context.Response.Redirect(returnUrl ?? "/"); + context.HandleResponse(); + + return Task.CompletedTask; + }; }); - } + + serviceCollection.AddSingleton(new ExternalSignInProviderConfiguration + { + AuthenticationType = options.AuthenticationType, + Provider = new OidcExternalSignInProvider(options), + LogoUrl = options.LogoUrl, + }); } } diff --git a/src/VirtoCommerce.OpenIdConnectModule.Web/VirtoCommerce.OpenIdConnectModule.Web.csproj b/src/VirtoCommerce.OpenIdConnectModule.Web/VirtoCommerce.OpenIdConnectModule.Web.csproj index 233ca1d..d921069 100644 --- a/src/VirtoCommerce.OpenIdConnectModule.Web/VirtoCommerce.OpenIdConnectModule.Web.csproj +++ b/src/VirtoCommerce.OpenIdConnectModule.Web/VirtoCommerce.OpenIdConnectModule.Web.csproj @@ -14,9 +14,7 @@ - - - + diff --git a/src/VirtoCommerce.OpenIdConnectModule.Web/module.manifest b/src/VirtoCommerce.OpenIdConnectModule.Web/module.manifest index e5cfe50..19c361a 100644 --- a/src/VirtoCommerce.OpenIdConnectModule.Web/module.manifest +++ b/src/VirtoCommerce.OpenIdConnectModule.Web/module.manifest @@ -4,16 +4,15 @@ 3.800.0 - 3.849.0 - - - + 3.865.0 + VirtoCommerce OpenIdConnect module VirtoCommerce OpenId Connect module Igoris Berniukevicius + Artem Dudarev VirtoCommerce diff --git a/tests/VirtoCommerce.OpenIdConnectModule.Tests/VirtoCommerce.OpenIdConnectModule.Tests.csproj b/tests/VirtoCommerce.OpenIdConnectModule.Tests/VirtoCommerce.OpenIdConnectModule.Tests.csproj index ac1ce94..be90d1f 100644 --- a/tests/VirtoCommerce.OpenIdConnectModule.Tests/VirtoCommerce.OpenIdConnectModule.Tests.csproj +++ b/tests/VirtoCommerce.OpenIdConnectModule.Tests/VirtoCommerce.OpenIdConnectModule.Tests.csproj @@ -12,9 +12,12 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - - - + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive +