From 36f268c970753d237a20ba6d614a31066a8e3223 Mon Sep 17 00:00:00 2001 From: colin Date: Sat, 7 Jun 2025 18:35:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(account):=20=E8=B4=A6=E6=88=B7=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E5=A2=9E=E5=8A=A0OAuth=E6=89=A9=E5=B1=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 增加OAuth模块 **AbpAccountOAuthModule** ,用于定义OAuth相关设置、功能. - 重写登录视图页第三方登录区域组件 - 增加扩展登录提供者服务接口 - 增加账户模块OAuth实现模块 **AbpAccountWebOAuthModule** - 重写OAuthHandler以实现从设置系统中读取客户端配置项 **AccountAuthenticationRequestHandler**、**IOAuthHandlerOptionsProvider** - 增加默认第三方登录实现: GitHub、QQ、微信、企业微信、Bilibili --- .../LINGYUN.Abp.Account.OAuth/FodyWeavers.xml | 3 + .../LINGYUN.Abp.Account.OAuth/FodyWeavers.xsd | 30 ++ .../LINGYUN.Abp.Account.OAuth.csproj | 26 ++ .../Account/OAuth/AbpAccountOAuthModule.cs | 28 ++ .../AccountOAuthFeatureDefinitionProvider.cs | 52 +++ .../Features/AccountOAuthFeatureNames.cs | 46 +++ .../Localization/AccountOAuthResource.cs | 8 + .../OAuth/Localization/Resources/en.json | 36 ++ .../OAuth/Localization/Resources/zh-Hans.json | 36 ++ .../AccountOAuthSettingDefinitionProvider.cs | 77 +++++ .../Settings/AccountOAuthSettingNames.cs | 30 ++ .../AbpAccountWebOAuthModule.cs | 105 ++++++ .../AccountAuthenticationRequestHandler.cs | 81 +++++ .../BilibiliAuthHandlerOptionsProvider.cs | 29 ++ .../GitHubAuthHandlerOptionsProvider.cs | 29 ++ .../IOAuthHandlerOptionsProvider.cs | 10 + .../OAuthExternalProviderService.cs | 68 ++++ .../OAuthHandlerOptionsProvider.cs | 18 + .../QQ/QQAuthHandlerOptionsProvider.cs | 29 ++ .../WeChatAuthHandlerOptionsProvider.cs | 29 ++ .../WeCom/WeComAuthHandlerOptionsProvider.cs | 34 ++ .../FodyWeavers.xml | 3 + .../FodyWeavers.xsd | 30 ++ .../LINGYUN.Abp.Account.Web.OAuth.csproj | 44 +++ .../AuthenticationBuilderExtensions.cs | 32 ++ .../ExternalProviders/Bilibili/Default.cshtml | 14 + .../ExternalProviderViewComponent.cs | 13 + .../ExternalProviders/GitHub/Default.cshtml | 14 + .../ExternalProviders/QQ/Default.cshtml | 14 + .../ExternalProviders/Weixin/Default.cshtml | 14 + .../WorkWeixin/Default.cshtml | 13 + .../Properties/launchSettings.json | 12 + .../wwwroot/images/bilibili_logo_18x18.png | Bin 0 -> 2085 bytes .../wwwroot/images/qq_logo_15x18.png | Bin 0 -> 628 bytes .../wwwroot/images/wecom_logo_77x18.png | Bin 0 -> 1022 bytes .../IExternalProviderService.cs | 10 + .../Models/ExternalLoginProviderModel.cs | 11 + .../Pages/Account/ChangePassword.cshtml.cs | 5 + .../Pages/Account/Login.cshtml | 15 +- .../Pages/Account/Login.cshtml.cs | 325 +++++++++++++++--- .../WeChatOffcialTokenExtensionGrant.cs | 23 +- ...N.Abp.SettingManagement.Application.csproj | 1 + .../AbpSettingManagementApplicationModule.cs | 10 +- .../SettingManagement/SettingAppService.cs | 46 ++- 44 files changed, 1383 insertions(+), 70 deletions(-) create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/FodyWeavers.xml create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/FodyWeavers.xsd create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN.Abp.Account.OAuth.csproj create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/AbpAccountOAuthModule.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Features/AccountOAuthFeatureDefinitionProvider.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Features/AccountOAuthFeatureNames.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Localization/AccountOAuthResource.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Localization/Resources/en.json create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Localization/Resources/zh-Hans.json create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Settings/AccountOAuthSettingDefinitionProvider.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Settings/AccountOAuthSettingNames.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/AbpAccountWebOAuthModule.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/AccountAuthenticationRequestHandler.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/Bilibili/BilibiliAuthHandlerOptionsProvider.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/GitHub/GitHubAuthHandlerOptionsProvider.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/IOAuthHandlerOptionsProvider.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/OAuthExternalProviderService.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/OAuthHandlerOptionsProvider.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/QQ/QQAuthHandlerOptionsProvider.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/WeChat/WeChatAuthHandlerOptionsProvider.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/WeCom/WeComAuthHandlerOptionsProvider.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/FodyWeavers.xml create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/FodyWeavers.xsd create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/LINGYUN.Abp.Account.Web.OAuth.csproj create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Microsoft/Extensions/DependencyInjection/AuthenticationBuilderExtensions.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/Bilibili/Default.cshtml create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/ExternalProviderViewComponent.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/GitHub/Default.cshtml create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/QQ/Default.cshtml create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/Weixin/Default.cshtml create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/WorkWeixin/Default.cshtml create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Properties/launchSettings.json create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/wwwroot/images/bilibili_logo_18x18.png create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/wwwroot/images/qq_logo_15x18.png create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/wwwroot/images/wecom_logo_77x18.png create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web/ExternalProviders/IExternalProviderService.cs create mode 100644 aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Models/ExternalLoginProviderModel.cs diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/FodyWeavers.xml b/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/FodyWeavers.xsd b/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN.Abp.Account.OAuth.csproj b/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN.Abp.Account.OAuth.csproj new file mode 100644 index 000000000..5766da178 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN.Abp.Account.OAuth.csproj @@ -0,0 +1,26 @@ + + + + + + + netstandard2.0;netstandard2.1;net8.0;net9.0 + LINGYUN.Abp.Account.OAuth + LINGYUN.Abp.Account.OAuth + false + false + false + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/AbpAccountOAuthModule.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/AbpAccountOAuthModule.cs new file mode 100644 index 000000000..77f9e9870 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/AbpAccountOAuthModule.cs @@ -0,0 +1,28 @@ +using LINGYUN.Abp.Account.OAuth.Localization; +using Volo.Abp.Features; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.Settings; +using Volo.Abp.VirtualFileSystem; + +namespace LINGYUN.Abp.Account.OAuth; + +[DependsOn(typeof(AbpFeaturesModule))] +[DependsOn(typeof(AbpSettingsModule))] +public class AbpAccountOAuthModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded(); + }); + + Configure(options => + { + options.Resources + .Add() + .AddVirtualJson("/LINGYUN/Abp/Account/OAuth/Localization/Resources"); + }); + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Features/AccountOAuthFeatureDefinitionProvider.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Features/AccountOAuthFeatureDefinitionProvider.cs new file mode 100644 index 000000000..9d0a3068c --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Features/AccountOAuthFeatureDefinitionProvider.cs @@ -0,0 +1,52 @@ +using LINGYUN.Abp.Account.OAuth.Localization; +using Volo.Abp.Features; +using Volo.Abp.Localization; +using Volo.Abp.Validation.StringValues; + +namespace LINGYUN.Abp.Account.OAuth.Features; + +public class AccountOAuthFeatureDefinitionProvider : FeatureDefinitionProvider +{ + public override void Define(IFeatureDefinitionContext context) + { + var group = context.AddGroup( + name: AccountOAuthFeatureNames.GroupName, + displayName: L("Features:ExternalOAuthLogin")); + + group.AddFeature( + name: AccountOAuthFeatureNames.GitHub.Enable, + defaultValue: "false", + displayName: L("Features:GithubOAuthEnable"), + description: L("Features:GithubOAuthEnableDesc"), + valueType: new ToggleStringValueType(new BooleanValueValidator())); + group.AddFeature( + name: AccountOAuthFeatureNames.QQ.Enable, + defaultValue: "false", + displayName: L("Features:QQOAuthEnable"), + description: L("Features:QQOAuthEnableDesc"), + valueType: new ToggleStringValueType(new BooleanValueValidator())); + group.AddFeature( + name: AccountOAuthFeatureNames.WeChat.Enable, + defaultValue: "false", + displayName: L("Features:WeChatOAuthEnable"), + description: L("Features:WeChatOAuthEnableDesc"), + valueType: new ToggleStringValueType(new BooleanValueValidator())); + group.AddFeature( + name: AccountOAuthFeatureNames.WeCom.Enable, + defaultValue: "false", + displayName: L("Features:WeComOAuthEnable"), + description: L("Features:WeComOAuthEnableDesc"), + valueType: new ToggleStringValueType(new BooleanValueValidator())); + group.AddFeature( + name: AccountOAuthFeatureNames.Bilibili.Enable, + defaultValue: "false", + displayName: L("Features:BilibiliOAuthEnable"), + description: L("Features:BilibiliOAuthEnableDesc"), + valueType: new ToggleStringValueType(new BooleanValueValidator())); + } + + private static LocalizableString L(string name) + { + return LocalizableString.Create(name); + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Features/AccountOAuthFeatureNames.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Features/AccountOAuthFeatureNames.cs new file mode 100644 index 000000000..4d97aabb1 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Features/AccountOAuthFeatureNames.cs @@ -0,0 +1,46 @@ +namespace LINGYUN.Abp.Account.OAuth.Features; + +public static class AccountOAuthFeatureNames +{ + public const string GroupName = "Abp.Account.OAuth"; + public static class GitHub + { + public const string Prefix = GroupName + ".GitHub"; + /// + /// 启用Github认证登录 + /// + public const string Enable = Prefix + ".Enable"; + } + public static class QQ + { + public const string Prefix = GroupName + ".QQ"; + /// + /// 启用QQ认证登录 + /// + public const string Enable = Prefix + ".Enable"; + } + public static class WeChat + { + public const string Prefix = GroupName + ".WeChat"; + /// + /// 启用微信认证登录 + /// + public const string Enable = Prefix + ".Enable"; + } + public static class WeCom + { + public const string Prefix = GroupName + ".WeCom"; + /// + /// 启用企业微信认证登录 + /// + public const string Enable = Prefix + ".Enable"; + } + public static class Bilibili + { + public const string Prefix = GroupName + ".Bilibili"; + /// + /// 启用Bilibili认证登录 + /// + public const string Enable = Prefix + ".Enable"; + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Localization/AccountOAuthResource.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Localization/AccountOAuthResource.cs new file mode 100644 index 000000000..32b981ed8 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Localization/AccountOAuthResource.cs @@ -0,0 +1,8 @@ +using Volo.Abp.Localization; + +namespace LINGYUN.Abp.Account.OAuth.Localization; + +[LocalizationResourceName("AbpAccountOAuth")] +public class AccountOAuthResource +{ +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Localization/Resources/en.json b/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Localization/Resources/en.json new file mode 100644 index 000000000..94849218d --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Localization/Resources/en.json @@ -0,0 +1,36 @@ +{ + "culture": "en", + "texts": { + "Permission:ExternalOAuthLogin": "External Oauth Login", + "Features:ExternalOAuthLogin": "External Oauth Login", + "Features:GithubOAuthEnable": "GitHub", + "Features:GithubOAuthEnableDesc": "Enable to enable the application to support login via a GitHub account.", + "Features:QQOAuthEnable": "QQ", + "Features:QQOAuthEnableDesc": "Enable to enable the application to support login via QQ account.", + "Features:WeChatOAuthEnable": "WeChat", + "Features:WeChatOAuthEnableDesc": "Enable to enable the application to support login via the wechat official account.", + "Features:WeComOAuthEnable": "WeCom", + "Features:WeComOAuthEnableDesc": "Enable to enable the application to support login via wecom.", + "Features:BilibiliOAuthEnable": "Bilibili", + "Features:BilibiliOAuthEnableDesc": "Enable to allow the application to support login via a Bilibili account.", + "Settings:ExternalOAuthLogin": "External Oauth Login", + "Settings:GitHubAuth": "GitHub", + "Settings:GitHubClientId": "Client Id", + "Settings:GitHubClientIdDesc": "The client ID received from GitHub during registration. for details: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps", + "Settings:GitHubClientSecret": "Client Secret", + "Settings:GitHubClientSecretDesc": "The client key of the OAuth application that you received from GitHub. for details: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps", + "Settings:BilibiliAuth": "Bilibili", + "Settings:BilibiliClientId": "Client Id", + "Settings:BilibiliClientIdDesc": "Client Id, for details: https://open.bilibili.com/doc/4/eaf0e2b5-bde9-b9a0-9be1-019bb455701c#h1-u7B80u4ECB", + "Settings:BilibiliClientSecret": "Client Secret", + "Settings:BilibiliClientSecretDesc": "Client Secret, for details: https://open.bilibili.com/doc/4/eaf0e2b5-bde9-b9a0-9be1-019bb455701c#h1-u7B80u4ECB", + "OAuth:Microsoft": "Microsoft", + "OAuth:Twitter": "Twitter", + "OAuth:GitHub": "GitHub", + "OAuth:Google": "Google", + "OAuth:QQ": "QQ", + "OAuth:Weixin": "WeChat", + "OAuth:WorkWeixin": "WeCom", + "OAuth:Bilibili": "Bilibili" + } +} \ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Localization/Resources/zh-Hans.json b/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Localization/Resources/zh-Hans.json new file mode 100644 index 000000000..859fe07b1 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Localization/Resources/zh-Hans.json @@ -0,0 +1,36 @@ +{ + "culture": "zh-Hans", + "texts": { + "Permission:ExternalOAuthLogin": "外部登录", + "Features:ExternalOAuthLogin": "外部登录", + "Features:GithubOAuthEnable": "GitHub认证", + "Features:GithubOAuthEnableDesc": "启用以使应用程序支持通过GitHub账号登录.", + "Features:QQOAuthEnable": "QQ认证", + "Features:QQOAuthEnableDesc": "启用以使应用程序支持通过QQ账号登录.", + "Features:WeChatOAuthEnable": "微信认证", + "Features:WeChatOAuthEnableDesc": "启用以使应用程序支持通过微信公众号登录.", + "Features:WeComOAuthEnable": "企业微信认证", + "Features:WeComOAuthEnableDesc": "启用以使应用程序支持通过企业微信登录.", + "Features:BilibiliOAuthEnable": "Bilibili认证", + "Features:BilibiliOAuthEnableDesc": "启用以使应用程序支持通过Bilibili账号登录.", + "Settings:ExternalOAuthLogin": "外部登录", + "Settings:GitHubAuth": "GitHub登录", + "Settings:GitHubClientId": "Client Id", + "Settings:GitHubClientIdDesc": "注册时从 GitHub 收到的客户端 ID.详见: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps", + "Settings:GitHubClientSecret": "Client Secret", + "Settings:GitHubClientSecretDesc": "您从 GitHub 收到的 OAuth 应用程序的客户端密钥.详见: https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps", + "Settings:BilibiliAuth": "Bilibili登录", + "Settings:BilibiliClientId": "Client Id", + "Settings:BilibiliClientIdDesc": "应用Id, 详见: https://open.bilibili.com/doc/4/eaf0e2b5-bde9-b9a0-9be1-019bb455701c#h1-u7B80u4ECB", + "Settings:BilibiliClientSecret": "Client Secret", + "Settings:BilibiliClientSecretDesc": "应用密钥, 详见: https://open.bilibili.com/doc/4/eaf0e2b5-bde9-b9a0-9be1-019bb455701c#h1-u7B80u4ECB", + "OAuth:Microsoft": "Microsoft", + "OAuth:Twitter": "Twitter", + "OAuth:GitHub": "GitHub", + "OAuth:Google": "Google", + "OAuth:QQ": "QQ", + "OAuth:Weixin": "微信", + "OAuth:WorkWeixin": "企业微信", + "OAuth:Bilibili": "Bilibili" + } +} \ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Settings/AccountOAuthSettingDefinitionProvider.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Settings/AccountOAuthSettingDefinitionProvider.cs new file mode 100644 index 000000000..dba71ecbf --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Settings/AccountOAuthSettingDefinitionProvider.cs @@ -0,0 +1,77 @@ +using LINGYUN.Abp.Account.OAuth.Localization; +using Volo.Abp.Localization; +using Volo.Abp.Settings; + +namespace LINGYUN.Abp.Account.OAuth.Settings; + +public class AccountOAuthSettingDefinitionProvider : SettingDefinitionProvider +{ + public override void Define(ISettingDefinitionContext context) + { + context.Add(GetGitHubSettings()); + context.Add(GetBilibiliSettings()); + } + + private SettingDefinition[] GetGitHubSettings() + { + return new SettingDefinition[] + { + new SettingDefinition( + AccountOAuthSettingNames.GitHub.ClientId, + displayName: L("Settings:GitHubClientId"), + description: L("Settings:GitHubClientIdDesc"), + isVisibleToClients: false, + isEncrypted: true) + .WithProviders( + DefaultValueSettingValueProvider.ProviderName, + ConfigurationSettingValueProvider.ProviderName, + GlobalSettingValueProvider.ProviderName, + TenantSettingValueProvider.ProviderName), + new SettingDefinition( + AccountOAuthSettingNames.GitHub.ClientSecret, + displayName: L("Settings:GitHubClientSecret"), + description: L("Settings:GitHubClientSecretDesc"), + isVisibleToClients: false, + isEncrypted: true) + .WithProviders( + DefaultValueSettingValueProvider.ProviderName, + ConfigurationSettingValueProvider.ProviderName, + GlobalSettingValueProvider.ProviderName, + TenantSettingValueProvider.ProviderName), + }; + } + + private SettingDefinition[] GetBilibiliSettings() + { + return new SettingDefinition[] + { + new SettingDefinition( + AccountOAuthSettingNames.Bilibili.ClientId, + displayName: L("Settings:BilibiliClientId"), + description: L("Settings:BilibiliClientIdDesc"), + isVisibleToClients: false, + isEncrypted: true) + .WithProviders( + DefaultValueSettingValueProvider.ProviderName, + ConfigurationSettingValueProvider.ProviderName, + GlobalSettingValueProvider.ProviderName, + TenantSettingValueProvider.ProviderName), + new SettingDefinition( + AccountOAuthSettingNames.Bilibili.ClientSecret, + displayName: L("Settings:BilibiliClientSecret"), + description: L("Settings:BilibiliClientSecretDesc"), + isVisibleToClients: false, + isEncrypted: true) + .WithProviders( + DefaultValueSettingValueProvider.ProviderName, + ConfigurationSettingValueProvider.ProviderName, + GlobalSettingValueProvider.ProviderName, + TenantSettingValueProvider.ProviderName), + }; + } + + protected ILocalizableString L(string name) + { + return LocalizableString.Create(name); + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Settings/AccountOAuthSettingNames.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Settings/AccountOAuthSettingNames.cs new file mode 100644 index 000000000..345b72bbf --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.OAuth/LINGYUN/Abp/Account/OAuth/Settings/AccountOAuthSettingNames.cs @@ -0,0 +1,30 @@ +namespace LINGYUN.Abp.Account.OAuth.Settings; + +public static class AccountOAuthSettingNames +{ + public const string GroupName = "Abp.Account.OAuth"; + public static class GitHub + { + public const string Prefix = GroupName + ".GitHub"; + /// + /// ClientId + /// + public const string ClientId = Prefix + ".ClientId"; + /// + /// ClientSecret + /// + public const string ClientSecret = Prefix + ".ClientSecret"; + } + public static class Bilibili + { + public const string Prefix = GroupName + ".Bilibili"; + /// + /// ClientId + /// + public const string ClientId = Prefix + ".ClientId"; + /// + /// ClientSecret + /// + public const string ClientSecret = Prefix + ".ClientSecret"; + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/AbpAccountWebOAuthModule.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/AbpAccountWebOAuthModule.cs new file mode 100644 index 000000000..e0be1f337 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/AbpAccountWebOAuthModule.cs @@ -0,0 +1,105 @@ +using AspNet.Security.OAuth.Bilibili; +using AspNet.Security.OAuth.GitHub; +using AspNet.Security.OAuth.QQ; +using AspNet.Security.OAuth.Weixin; +using AspNet.Security.OAuth.WorkWeixin; +using LINGYUN.Abp.Account.OAuth; +using LINGYUN.Abp.Account.OAuth.Localization; +using LINGYUN.Abp.Account.Web.OAuth.ExternalProviders.Bilibili; +using LINGYUN.Abp.Account.Web.OAuth.ExternalProviders.GitHub; +using LINGYUN.Abp.Account.Web.OAuth.ExternalProviders.QQ; +using LINGYUN.Abp.Account.Web.OAuth.ExternalProviders.WeChat; +using LINGYUN.Abp.Account.Web.OAuth.ExternalProviders.WeCom; +using LINGYUN.Abp.Account.Web.OAuth.Microsoft.Extensions.DependencyInjection; +using LINGYUN.Abp.Tencent.QQ; +using LINGYUN.Abp.WeChat.Official; +using LINGYUN.Abp.WeChat.Work; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Account.Localization; +using Volo.Abp.AspNetCore.Mvc.Localization; +using Volo.Abp.Localization; +using Volo.Abp.Modularity; +using Volo.Abp.VirtualFileSystem; + +namespace LINGYUN.Abp.Account.Web.OAuth; + +[DependsOn(typeof(AbpAccountWebModule))] +[DependsOn(typeof(AbpAccountOAuthModule))] +[DependsOn(typeof(AbpTencentQQModule))] +[DependsOn(typeof(AbpWeChatOfficialModule))] +[DependsOn(typeof(AbpWeChatWorkModule))] +public class AbpAccountWebOAuthModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + context.Services.PreConfigure(options => + { + options.AddAssemblyResource(typeof(AccountResource), typeof(AbpAccountWebOAuthModule).Assembly); + }); + + PreConfigure(mvcBuilder => + { + mvcBuilder.AddApplicationPartIfNotExists(typeof(AbpAccountWebOAuthModule).Assembly); + }); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.FileSets.AddEmbedded("LINGYUN.Abp.Account.Web.OAuth"); + }); + + Configure(options => + { + options.Resources + .Get() + .AddBaseTypes(typeof(AccountOAuthResource)); + }); + + context.Services + .AddAuthentication() + .AddGitHub(options => + { + options.ClientId = "ClientId"; + options.ClientSecret = "ClientSecret"; + + options.Scope.Add("user:email"); + }).UseSettingProvider< + GitHubAuthenticationOptions, + GitHubAuthenticationHandler, + GitHubAuthHandlerOptionsProvider>() + .AddQQ(options => + { + options.ClientId = "ClientId"; + options.ClientSecret = "ClientSecret"; + }).UseSettingProvider< + QQAuthenticationOptions, + QQAuthenticationHandler, + QQAuthHandlerOptionsProvider>() + .AddWeixin(options => + { + options.ClientId = "ClientId"; + options.ClientSecret = "ClientSecret"; + }).UseSettingProvider< + WeixinAuthenticationOptions, + WeixinAuthenticationHandler, + WeChatAuthHandlerOptionsProvider>() + .AddWorkWeixin(options => + { + options.ClientId = "ClientId"; + options.ClientSecret = "ClientSecret"; + }).UseSettingProvider< + WorkWeixinAuthenticationOptions, + WorkWeixinAuthenticationHandler, + WeComAuthHandlerOptionsProvider>() + .AddBilibili(options => + { + options.ClientId = "ClientId"; + options.ClientSecret = "ClientSecret"; + }).UseSettingProvider< + BilibiliAuthenticationOptions, + BilibiliAuthenticationHandler, + BilibiliAuthHandlerOptionsProvider>(); + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/AccountAuthenticationRequestHandler.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/AccountAuthenticationRequestHandler.cs new file mode 100644 index 000000000..1601087fb --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/AccountAuthenticationRequestHandler.cs @@ -0,0 +1,81 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using System; +using System.Security.Claims; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Account.Web.OAuth.ExternalProviders; + +public class AccountAuthenticationRequestHandler : IAuthenticationRequestHandler + where TOptions : RemoteAuthenticationOptions, new() + where THandler : RemoteAuthenticationHandler +{ + protected THandler InnerHandler { get; } + protected IOAuthHandlerOptionsProvider OptionsProvider { get; } + public AccountAuthenticationRequestHandler( + THandler innerHandler, + IOAuthHandlerOptionsProvider optionsProvider) + { + InnerHandler = innerHandler; + OptionsProvider = optionsProvider; + } + + public virtual async Task InitializeAsync(AuthenticationScheme scheme, HttpContext context) + { + await InnerHandler.InitializeAsync(scheme, context); + } + + public virtual async Task AuthenticateAsync() + { + return await InnerHandler.AuthenticateAsync(); + } + + public virtual async Task ChallengeAsync(AuthenticationProperties? properties) + { + await InitializeOptionsAsync(); + + await InnerHandler.ChallengeAsync(properties); + } + + public virtual async Task ForbidAsync(AuthenticationProperties? properties) + { + await InnerHandler.ForbidAsync(properties); + } + + public async Task SignOutAsync(AuthenticationProperties properties) + { + if (!(InnerHandler is IAuthenticationSignOutHandler signOutHandler)) + { + throw new InvalidOperationException($"The authentication handler registered for scheme '{InnerHandler.Scheme}' is '{InnerHandler.GetType().Name}' which cannot be used for SignOutAsync"); + } + + await InitializeOptionsAsync(); + await signOutHandler.SignOutAsync(properties); + } + + public async Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties properties) + { + if (!(InnerHandler is IAuthenticationSignInHandler signInHandler)) + { + throw new InvalidOperationException($"The authentication handler registered for scheme '{InnerHandler.Scheme}' is '{InnerHandler.GetType().Name}' which cannot be used for SignInAsync"); + } + + await InitializeOptionsAsync(); + await signInHandler.SignInAsync(user, properties); + } + + public virtual async Task HandleRequestAsync() + { + if (await InnerHandler.ShouldHandleRequestAsync()) + { + await InitializeOptionsAsync(); + } + + return await InnerHandler.HandleRequestAsync(); + } + + protected async virtual Task InitializeOptionsAsync() + { + await OptionsProvider.SetOptionsAsync(InnerHandler.Options); + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/Bilibili/BilibiliAuthHandlerOptionsProvider.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/Bilibili/BilibiliAuthHandlerOptionsProvider.cs new file mode 100644 index 000000000..933a4e634 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/Bilibili/BilibiliAuthHandlerOptionsProvider.cs @@ -0,0 +1,29 @@ +using AspNet.Security.OAuth.Bilibili; +using LINGYUN.Abp.Account.OAuth.Settings; +using System; +using System.Threading.Tasks; +using Volo.Abp.Settings; + +namespace LINGYUN.Abp.Account.Web.OAuth.ExternalProviders.Bilibili; + +public class BilibiliAuthHandlerOptionsProvider : OAuthHandlerOptionsProvider +{ + public BilibiliAuthHandlerOptionsProvider(ISettingProvider settingProvider) : base(settingProvider) + { + } + + public async override Task SetOptionsAsync(BilibiliAuthenticationOptions options) + { + var clientId = await SettingProvider.GetOrNullAsync(AccountOAuthSettingNames.Bilibili.ClientId); + var clientSecret = await SettingProvider.GetOrNullAsync(AccountOAuthSettingNames.Bilibili.ClientSecret); + + if (!clientId.IsNullOrWhiteSpace()) + { + options.ClientId = clientId; + } + if (!clientSecret.IsNullOrWhiteSpace()) + { + options.ClientSecret = clientSecret; + } + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/GitHub/GitHubAuthHandlerOptionsProvider.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/GitHub/GitHubAuthHandlerOptionsProvider.cs new file mode 100644 index 000000000..4bfd43fcf --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/GitHub/GitHubAuthHandlerOptionsProvider.cs @@ -0,0 +1,29 @@ +using AspNet.Security.OAuth.GitHub; +using LINGYUN.Abp.Account.OAuth.Settings; +using System; +using System.Threading.Tasks; +using Volo.Abp.Settings; + +namespace LINGYUN.Abp.Account.Web.OAuth.ExternalProviders.GitHub; + +public class GitHubAuthHandlerOptionsProvider : OAuthHandlerOptionsProvider +{ + public GitHubAuthHandlerOptionsProvider(ISettingProvider settingProvider) : base(settingProvider) + { + } + + public async override Task SetOptionsAsync(GitHubAuthenticationOptions options) + { + var clientId = await SettingProvider.GetOrNullAsync(AccountOAuthSettingNames.GitHub.ClientId); + var clientSecret = await SettingProvider.GetOrNullAsync(AccountOAuthSettingNames.GitHub.ClientSecret); + + if (!clientId.IsNullOrWhiteSpace()) + { + options.ClientId = clientId; + } + if (!clientSecret.IsNullOrWhiteSpace()) + { + options.ClientSecret = clientSecret; + } + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/IOAuthHandlerOptionsProvider.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/IOAuthHandlerOptionsProvider.cs new file mode 100644 index 000000000..b845a34e5 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/IOAuthHandlerOptionsProvider.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Authentication; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Account.Web.OAuth.ExternalProviders; + +public interface IOAuthHandlerOptionsProvider + where TOptions : RemoteAuthenticationOptions, new() +{ + Task SetOptionsAsync(TOptions options); +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/OAuthExternalProviderService.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/OAuthExternalProviderService.cs new file mode 100644 index 000000000..e35a66c3b --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/OAuthExternalProviderService.cs @@ -0,0 +1,68 @@ +using AspNet.Security.OAuth.Bilibili; +using AspNet.Security.OAuth.GitHub; +using AspNet.Security.OAuth.QQ; +using AspNet.Security.OAuth.Weixin; +using AspNet.Security.OAuth.WorkWeixin; +using LINGYUN.Abp.Account.OAuth.Features; +using LINGYUN.Abp.Account.Web.ExternalProviders; +using LINGYUN.Abp.Account.Web.Models; +using LINGYUN.Abp.Account.Web.OAuth.Pages.Account.Components.ExternalProviders; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Localization; +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Account.Localization; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; + +namespace LINGYUN.Abp.Account.Web.OAuth.ExternalProviders; + +public class OAuthExternalProviderService : IExternalProviderService, ITransientDependency +{ + private static readonly Dictionary _providerFeaturesMap = new Dictionary + { + [GitHubAuthenticationDefaults.AuthenticationScheme] = AccountOAuthFeatureNames.GitHub.Enable, + [QQAuthenticationDefaults.AuthenticationScheme] = AccountOAuthFeatureNames.QQ.Enable, + [WeixinAuthenticationDefaults.AuthenticationScheme] = AccountOAuthFeatureNames.WeChat.Enable, + [WorkWeixinAuthenticationDefaults.AuthenticationScheme] = AccountOAuthFeatureNames.WeCom.Enable, + [BilibiliAuthenticationDefaults.AuthenticationScheme] = AccountOAuthFeatureNames.Bilibili.Enable + }; + + private readonly IFeatureChecker _featureChecker; + private readonly IStringLocalizer _stringLocalizer; + private readonly IAuthenticationSchemeProvider _authenticationSchemeProvider; + public OAuthExternalProviderService( + IFeatureChecker featureChecker, + IStringLocalizer stringLocalizer, + IAuthenticationSchemeProvider authenticationSchemeProvider) + { + _featureChecker = featureChecker; + _stringLocalizer = stringLocalizer; + _authenticationSchemeProvider = authenticationSchemeProvider; + } + public async virtual Task> GetAllAsync() + { + var models = new List(); + + var schemas = await _authenticationSchemeProvider.GetAllSchemesAsync(); + + foreach (var schema in schemas) + { + if (_providerFeaturesMap.TryGetValue(schema.Name, out var schemaFeature)) + { + if (await _featureChecker.IsEnabledAsync(schemaFeature)) + { + models.Add(new ExternalLoginProviderModel + { + Name = schema.Name, + AuthenticationScheme = schema.Name, + DisplayName = _stringLocalizer[$"OAuth:{schema.Name}"], + ComponentType = typeof(ExternalProviderViewComponent), + }); + } + } + } + + return models; + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/OAuthHandlerOptionsProvider.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/OAuthHandlerOptionsProvider.cs new file mode 100644 index 000000000..1fdd23695 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/OAuthHandlerOptionsProvider.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Authentication; +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Settings; + +namespace LINGYUN.Abp.Account.Web.OAuth.ExternalProviders; + +public abstract class OAuthHandlerOptionsProvider : IOAuthHandlerOptionsProvider, ITransientDependency + where TOptions : RemoteAuthenticationOptions, new() +{ + protected ISettingProvider SettingProvider { get; } + public OAuthHandlerOptionsProvider(ISettingProvider settingProvider) + { + SettingProvider = settingProvider; + } + + public abstract Task SetOptionsAsync(TOptions options); +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/QQ/QQAuthHandlerOptionsProvider.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/QQ/QQAuthHandlerOptionsProvider.cs new file mode 100644 index 000000000..76f3b7b41 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/QQ/QQAuthHandlerOptionsProvider.cs @@ -0,0 +1,29 @@ +using AspNet.Security.OAuth.QQ; +using LINGYUN.Abp.Tencent.QQ.Settings; +using System; +using System.Threading.Tasks; +using Volo.Abp.Settings; + +namespace LINGYUN.Abp.Account.Web.OAuth.ExternalProviders.QQ; + +public class QQAuthHandlerOptionsProvider : OAuthHandlerOptionsProvider +{ + public QQAuthHandlerOptionsProvider(ISettingProvider settingProvider) : base(settingProvider) + { + } + + public async override Task SetOptionsAsync(QQAuthenticationOptions options) + { + var clientId = await SettingProvider.GetOrNullAsync(TencentQQSettingNames.QQConnect.AppId); + var clientSecret = await SettingProvider.GetOrNullAsync(TencentQQSettingNames.QQConnect.AppKey); + + if (!clientId.IsNullOrWhiteSpace()) + { + options.ClientId = clientId; + } + if (!clientSecret.IsNullOrWhiteSpace()) + { + options.ClientSecret = clientSecret; + } + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/WeChat/WeChatAuthHandlerOptionsProvider.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/WeChat/WeChatAuthHandlerOptionsProvider.cs new file mode 100644 index 000000000..7382370ef --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/WeChat/WeChatAuthHandlerOptionsProvider.cs @@ -0,0 +1,29 @@ +using AspNet.Security.OAuth.Weixin; +using LINGYUN.Abp.WeChat.Official.Settings; +using System; +using System.Threading.Tasks; +using Volo.Abp.Settings; + +namespace LINGYUN.Abp.Account.Web.OAuth.ExternalProviders.WeChat; + +public class WeChatAuthHandlerOptionsProvider : OAuthHandlerOptionsProvider +{ + public WeChatAuthHandlerOptionsProvider(ISettingProvider settingProvider) : base(settingProvider) + { + } + + public async override Task SetOptionsAsync(WeixinAuthenticationOptions options) + { + var clientId = await SettingProvider.GetOrNullAsync(WeChatOfficialSettingNames.AppId); + var clientSecret = await SettingProvider.GetOrNullAsync(WeChatOfficialSettingNames.AppSecret); + + if (!clientId.IsNullOrWhiteSpace()) + { + options.ClientId = clientId; + } + if (!clientSecret.IsNullOrWhiteSpace()) + { + options.ClientSecret = clientSecret; + } + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/WeCom/WeComAuthHandlerOptionsProvider.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/WeCom/WeComAuthHandlerOptionsProvider.cs new file mode 100644 index 000000000..45d077871 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/ExternalProviders/WeCom/WeComAuthHandlerOptionsProvider.cs @@ -0,0 +1,34 @@ +using AspNet.Security.OAuth.WorkWeixin; +using LINGYUN.Abp.WeChat.Work.Settings; +using System; +using System.Threading.Tasks; +using Volo.Abp.Settings; + +namespace LINGYUN.Abp.Account.Web.OAuth.ExternalProviders.WeCom; + +public class WeComAuthHandlerOptionsProvider : OAuthHandlerOptionsProvider +{ + public WeComAuthHandlerOptionsProvider(ISettingProvider settingProvider) : base(settingProvider) + { + } + + public async override Task SetOptionsAsync(WorkWeixinAuthenticationOptions options) + { + var clientId = await SettingProvider.GetOrNullAsync(WeChatWorkSettingNames.Connection.CorpId); + var clientSecret = await SettingProvider.GetOrNullAsync(WeChatWorkSettingNames.Connection.Secret); + var agentId = await SettingProvider.GetOrNullAsync(WeChatWorkSettingNames.Connection.AgentId); + + if (!clientId.IsNullOrWhiteSpace()) + { + options.ClientId = clientId; + } + if (!clientSecret.IsNullOrWhiteSpace()) + { + options.ClientSecret = clientSecret; + } + if (!agentId.IsNullOrWhiteSpace()) + { + options.AgentId = agentId; + } + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/FodyWeavers.xml b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/FodyWeavers.xml new file mode 100644 index 000000000..1715698cc --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/FodyWeavers.xsd b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/FodyWeavers.xsd new file mode 100644 index 000000000..3f3946e28 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/LINGYUN.Abp.Account.Web.OAuth.csproj b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/LINGYUN.Abp.Account.Web.OAuth.csproj new file mode 100644 index 000000000..ce4c135c6 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/LINGYUN.Abp.Account.Web.OAuth.csproj @@ -0,0 +1,44 @@ + + + + + + + net9.0 + LINGYUN.Abp.Account.Web.OAuth + LINGYUN.Abp.Account.Web.OAuth + false + false + false + LINGYUN.Abp.Account.Web.OAuth + Library + true + enable + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Microsoft/Extensions/DependencyInjection/AuthenticationBuilderExtensions.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Microsoft/Extensions/DependencyInjection/AuthenticationBuilderExtensions.cs new file mode 100644 index 000000000..8e9749770 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Microsoft/Extensions/DependencyInjection/AuthenticationBuilderExtensions.cs @@ -0,0 +1,32 @@ +using JetBrains.Annotations; +using LINGYUN.Abp.Account.Web.OAuth.ExternalProviders; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using System; +using System.Linq; +using Volo.Abp; + +namespace LINGYUN.Abp.Account.Web.OAuth.Microsoft.Extensions.DependencyInjection; + +public static class AuthenticationBuilderExtensions +{ + public static AuthenticationBuilder UseSettingProvider( + [NotNull] this AuthenticationBuilder authenticationBuilder) + where TOptions : RemoteAuthenticationOptions, new() + where THandler : RemoteAuthenticationHandler + where TOptionsProvider : IOAuthHandlerOptionsProvider + { + Check.NotNull(authenticationBuilder, nameof(authenticationBuilder)); + + var handler = authenticationBuilder.Services.LastOrDefault(x => x.ServiceType == typeof(THandler)); + authenticationBuilder.Services.Replace(new ServiceDescriptor( + typeof(THandler), + provider => new AccountAuthenticationRequestHandler( + (THandler)ActivatorUtilities.CreateInstance(provider, typeof(THandler)), + provider.GetRequiredService()), + handler?.Lifetime ?? ServiceLifetime.Transient)); + + return authenticationBuilder; + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/Bilibili/Default.cshtml b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/Bilibili/Default.cshtml new file mode 100644 index 000000000..74174a1c8 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/Bilibili/Default.cshtml @@ -0,0 +1,14 @@ +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.Account.Localization +@inject IHtmlLocalizer L +@model LINGYUN.Abp.Account.Web.Models.ExternalLoginProviderModel + + diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/ExternalProviderViewComponent.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/ExternalProviderViewComponent.cs new file mode 100644 index 000000000..a28515a41 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/ExternalProviderViewComponent.cs @@ -0,0 +1,13 @@ +using LINGYUN.Abp.Account.Web.Models; +using Microsoft.AspNetCore.Mvc; +using Volo.Abp.AspNetCore.Mvc; + +namespace LINGYUN.Abp.Account.Web.OAuth.Pages.Account.Components.ExternalProviders; + +public class ExternalProviderViewComponent : AbpViewComponent +{ + public virtual IViewComponentResult Invoke(ExternalLoginProviderModel model) + { + return View($"~/Pages/Account/Components/ExternalProviders/{model.AuthenticationScheme}/Default.cshtml", model); + } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/GitHub/Default.cshtml b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/GitHub/Default.cshtml new file mode 100644 index 000000000..314b8152e --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/GitHub/Default.cshtml @@ -0,0 +1,14 @@ +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.Account.Localization +@inject IHtmlLocalizer L +@model LINGYUN.Abp.Account.Web.Models.ExternalLoginProviderModel + + diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/QQ/Default.cshtml b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/QQ/Default.cshtml new file mode 100644 index 000000000..dff3f36f7 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/QQ/Default.cshtml @@ -0,0 +1,14 @@ +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.Account.Localization +@inject IHtmlLocalizer L +@model LINGYUN.Abp.Account.Web.Models.ExternalLoginProviderModel + + \ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/Weixin/Default.cshtml b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/Weixin/Default.cshtml new file mode 100644 index 000000000..c94fa9662 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/Weixin/Default.cshtml @@ -0,0 +1,14 @@ +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.Account.Localization +@inject IHtmlLocalizer L +@model LINGYUN.Abp.Account.Web.Models.ExternalLoginProviderModel + + diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/WorkWeixin/Default.cshtml b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/WorkWeixin/Default.cshtml new file mode 100644 index 000000000..1170c32b6 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Pages/Account/Components/ExternalProviders/WorkWeixin/Default.cshtml @@ -0,0 +1,13 @@ +@using Microsoft.AspNetCore.Mvc.Localization +@using Volo.Abp.Account.Localization +@inject IHtmlLocalizer L +@model LINGYUN.Abp.Account.Web.Models.ExternalLoginProviderModel + + diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Properties/launchSettings.json b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Properties/launchSettings.json new file mode 100644 index 000000000..0b8d5f6e6 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "LINGYUN.Abp.Account.Web.OAuth": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:50897;http://localhost:50898" + } + } +} \ No newline at end of file diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/wwwroot/images/bilibili_logo_18x18.png b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/wwwroot/images/bilibili_logo_18x18.png new file mode 100644 index 0000000000000000000000000000000000000000..610a0d31ad3c44e885d97bf600bb2211072709cd GIT binary patch literal 2085 zcmbtVX;hQP8lJFdkW>s1Zr~tW0R=(Xg%(gl2(pQXum&uUkRV17!{T9+iY$T4CZIwA zfdCD%D+wrGZEc}o4YCvn>m^4jTq|4cB@njUN$oxK{_y_kGiPR=ndf=mdB6F-b3XC7 zSO5;W5S$4BfdBvj8xX%nj5|9x1fBQvAUL}@eh~Tqgp3IQ5s@(rPbZv~pFc@!zu-fJ zL<^xrM@i<70Ol2rNwNd*lkrD9|8H9^lp0NehE>>g8SrpetTN=vVITNW2`7Kxwi13V zCMpJc&PzDM%hLh!P{@yl{fU$R#3@k>i9ZK=Y$GnmO3so%QZb4e=}m+)5_WYE4LpGp zz)7ATt|6|l0I*&IK)PUWk6a2sXF34b$-O<@DgYGH0O%Ol+uOU7s1QcT@9AXVC`F?I z@SY98C;k8^4FYf=;CBrUAE`|XO6st#E3k!u%YX{BfHQ~$6mT3eGhhNtft7d)IKbEW z*C0vq*D}Cp@wWht1d2dW8i58Z_ntyAmh}-G36K7(+lmf(+k{<&XYv*%mKSVqnxGdKQ>&9v$2R>^5OLJ z%u2VRo+k}Y-zT6nLutI7! z3OB!Sv&EOA;(FukFlD|Nl;eW$y*B&mO8?yoIre@17444ZE**~Q(z~=AN>3}N*ss=B zJCA{yb5ibfmC<{2b%Z&0`6bFl3Vy~}?vR;#$t2UwM z+tL=409l)2wjnAUIrX!{k7b0S7?T9UyHlR+C%kZ@R7{K7=_bB=VR3)D-Y`Ajjp3ay zzt;=V+xX6@m(*&|yP#nHb0R-RzHm+RgwXxfu$y;Axozp&x|_+YHnsM1vh`;DAs#0B zs9J(dP2)G5sxK&k83(7r*kK9U*X-si5BGJ}E^O@veQFd-Jkw>cEBym2!>{U>Wwc}U zrP2+h^jn(QGY3j`oNQK7BQ{vuWql@3&4=l$J9l%}XO5gsT53)slX7EYHam(u$lOUD zwE@){62zga)qZnw>`2b{qlej?mFo&^i>Ej3Q;4B;{5cV!sKR&tmvv0~LS8*bYs>#d zTvBjWyS*sPU^RbN=hoTFWPJ62#UVp#eA2bnnv!b=?GsrxUtPc)?)>6G;o6LQu@qhE z^;^!dugj$b`NCE)P;@^r3Br^HEB3RKIg=~&gqL9Nsgamyz$qr$bMC$v~?$F!;zBRUAep zH$6aCSHXzUEo|T}CIc@k}MzL6Bx98BkQP~*xQ~cDx zR6yas6OaF`H?~V@ny;Do^}Ju>yQ07!tgNh`xRP+M^mw_=6rb>aHd9C=UUruu*~a1qB0r1;wG=S@>=@36i>Z?6t8W)y%8CsR%)~ zb#N-SCL0@)Ro}&4hRE6 zvN1C#Dk>>UfOzfvqKrs#f8;b=IJo6S8Gfwec)N@pDAQb58f40Fu$3VxNDrd$g|uoB zpAe7@WI#j;c|qKXX)Kc`O_-d{0ucm~|NsAIVq|zHt*WlB@&DhyZa%TNxVT%2TJMx~ zf6QIf-rBlaMEbIX;!6>^8#iw35S3M8VVfr?CC0(j)7u*n7VK@xWTDE`P~UL(-h<=a zESX-+zAkz%7qLv4GOaSxT~bo=>#X_R`GsHFd;Xp}^L@jX_Yv_QlC!?A+w?Io@^egb zdwOPFB+JK@?Dl%>8#is4J$v?q2@}??UAyAnu~eYrv`d2gf*B&eMDz4BO1k~vRASlH zn6*)5<1+mlm%ar={d{fyAaD6Slb6{`etrIR=JZeY;$t6y+8%nkIEF}EHa&NB+F=78 zmVldnv!+N+m$dQ?N_(bf{O^DH6~D6QZ|5mzx-3bWamM6@yd)|IYl_%bI<7 zrn_?U{QTGD4L#crHC#RND~j=61E0>d`L9_Q?7r@2I7`=l+Gat%noA2^w%gC!fByL2 zJSWp5{IloIdu*}SuUKEN_R@+FZ>_6d$@4w9AHQAtwIaBB-+tGK#fy`##7CFA&SWer z(>(Qa$+s^HVxHT03EGBj%Z=VCb6HvB%8dPMLvnu46q~}@_@qF~QdW)m-F~Ioz!US* nxzjTrTB*9&r9b=Rw4`I(`5y|i{Xf4<21TQ%tDnm{r-UW|J5%-_ literal 0 HcmV?d00001 diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/wwwroot/images/wecom_logo_77x18.png b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web.OAuth/wwwroot/images/wecom_logo_77x18.png new file mode 100644 index 0000000000000000000000000000000000000000..5f828dcc7ac554e3f4525cc9d2f47252db4afa9c GIT binary patch literal 1022 zcmeAS@N?(olHy`uVBq!ia0vp^zCbL*!3-od`4=w$QfC5uLR{Sod>ESEGt_@zYkSPl z_#VWDp!&B^M&mmq@ee>j<|eQdvSO&Ncjhgp8Ja#IsfU>c6l{9W()t8yCfpX7A$CV) z4Yv9Gox||!G$T;W?+nKOXBhvaG1wiI@i{7OcSQDQ8uR}%41dxX|E4pX++$o+*7UcN z(e|*+|1<`$N|1Ukn-?d5{&Fe_@(X6@X5o_9o4kOrEQd|acKe1o^5ML(PLnTcFK22L zI(FvT?RKvRmae9?A-;w$f4vEjNqX`L%h! zyQF6wOo~>xH^q~yOZi*ny!TILi(fc0J*<*9exAJAD~5>y8OiR_0w2AUSBdUOHcyK@ z7W?kF^oRG(H#T{OoU3rv&+MA4|9Sd}%ZeeQ5;@||OT@U(YH_ldcrVtF5)$JwSgkEz zqtfDVStR_S{g&Bzk+aj^RC#Li#7sDusXZ&x=kTS>ch-K(Obs6!99haQHus5ll(t^N z?AfoM%V+cCa6P=Z?X8E{uG49=9tH$#QPy3vLi*U5n@oB+Kkq*B+!?bmI<|!=Zr%F$ z@L4%L*~fnFQF--7+f&+0nCbq~u%y$h8C!d%DP_Ja3sR}IHaS$0e)rzpy>|OGW()h) z+I#)2@TQk|XKFa;Cne_GBs~JpuuF3D`gq@IB8W7<3SUFhx zT;&qxM@*Gk?(h8mNu@>kaY(MRT;(I`8$7l2+t4D3gVs*nt+QnN-fw-YaX4M_!-sF5Z+~)MG+&kbvCd1cSH9O?UE0FH z{EkDQc#`?;q_64a^Xm53Huhh7_VBatzy5#w8?ISB-|o6x1(@&|JYD@<);T3K0RRZ7 B-pc?0 literal 0 HcmV?d00001 diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/ExternalProviders/IExternalProviderService.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/ExternalProviders/IExternalProviderService.cs new file mode 100644 index 000000000..3ba0087b3 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/ExternalProviders/IExternalProviderService.cs @@ -0,0 +1,10 @@ +using LINGYUN.Abp.Account.Web.Models; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LINGYUN.Abp.Account.Web.ExternalProviders; + +public interface IExternalProviderService +{ + Task> GetAllAsync(); +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Models/ExternalLoginProviderModel.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Models/ExternalLoginProviderModel.cs new file mode 100644 index 000000000..506398975 --- /dev/null +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Models/ExternalLoginProviderModel.cs @@ -0,0 +1,11 @@ +using System; + +namespace LINGYUN.Abp.Account.Web.Models; + +public class ExternalLoginProviderModel +{ + public Type ComponentType { get; set; } + public string Name { get; set; } + public string DisplayName { get; set; } + public string AuthenticationScheme { get; set; } +} diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/ChangePassword.cshtml.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/ChangePassword.cshtml.cs index e90aa16f6..9d54fb631 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/ChangePassword.cshtml.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/ChangePassword.cshtml.cs @@ -90,6 +90,11 @@ public class ChangePasswordModel : AccountPageModel Alerts.Warning(L["NewPasswordSameAsOld"]); return Page(); } + if (Input.NewPassword != Input.NewPasswordConfirm) + { + Alerts.Warning(L["NewPasswordConfirmFailed"]); + return Page(); + } var userInfo = await GetCurrentUser(); if (userInfo != null) diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml index f1e346cad..06a4e0835 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml @@ -74,7 +74,20 @@
@foreach (var provider in Model.VisibleExternalProviders) { - + @* *@ + @await Component.InvokeAsync(provider.ComponentType, provider); }
diff --git a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml.cs b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml.cs index 4c8c28d64..eb331a83a 100644 --- a/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml.cs +++ b/aspnet-core/modules/account/LINGYUN.Abp.Account.Web/Pages/Account/Login.cshtml.cs @@ -1,6 +1,9 @@ +using LINGYUN.Abp.Account.Web.ExternalProviders; +using LINGYUN.Abp.Account.Web.Models; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System; using System.Collections.Generic; @@ -8,30 +11,81 @@ using System.Diagnostics; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; +using Volo.Abp; using Volo.Abp.Account.Settings; using Volo.Abp.Account.Web; +using Volo.Abp.Account.Web.Pages.Account; using Volo.Abp.DependencyInjection; using Volo.Abp.Identity; using Volo.Abp.Identity.AspNetCore; +using Volo.Abp.Reflection; using Volo.Abp.Security.Claims; using Volo.Abp.Settings; +using Volo.Abp.Validation; +using static Volo.Abp.Account.Web.Pages.Account.LoginModel; using IdentityUser = Volo.Abp.Identity.IdentityUser; namespace LINGYUN.Abp.Account.Web.Pages.Account; -[ExposeServices(typeof(Volo.Abp.Account.Web.Pages.Account.LoginModel))] -public class LoginModel : Volo.Abp.Account.Web.Pages.Account.LoginModel +//[ExposeServices(typeof(Volo.Abp.Account.Web.Pages.Account.LoginModel))] +public class LoginModel : AccountPageModel { + [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; } + + public bool ShowCancelButton { get; set; } + public bool IsExternalLoginOnly => EnableLocalLogin == false && ExternalProviders?.Count() == 1; + public string ExternalLoginScheme => IsExternalLoginOnly ? ExternalProviders?.SingleOrDefault()?.AuthenticationScheme : null; + + public IEnumerable ExternalProviders { get; set; } + public IEnumerable VisibleExternalProviders => ExternalProviders.Where(x => !x.DisplayName.IsNullOrWhiteSpace()); + + + protected IExternalProviderService ExternalProviderService { get; } + protected IAuthenticationSchemeProvider SchemeProvider { get; } + protected AbpAccountOptions AccountOptions { get; } + protected IdentityDynamicClaimsPrincipalContributorCache IdentityDynamicClaimsPrincipalContributorCache { get; } public LoginModel( + IExternalProviderService externalProviderService, IAuthenticationSchemeProvider schemeProvider, IOptions accountOptions, IOptions identityOptions, IdentityDynamicClaimsPrincipalContributorCache identityDynamicClaimsPrincipalContributorCache) - : base(schemeProvider, accountOptions, identityOptions, identityDynamicClaimsPrincipalContributorCache) { + ExternalProviderService = externalProviderService; + SchemeProvider = schemeProvider; + IdentityOptions = identityOptions; + AccountOptions = accountOptions.Value; + IdentityDynamicClaimsPrincipalContributorCache = identityDynamicClaimsPrincipalContributorCache; } - public async override Task OnPostAsync(string action) + public virtual async Task OnGetAsync() + { + LoginInput = new LoginInputModel(); + + ExternalProviders = await GetExternalProviders(); + + EnableLocalLogin = await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin); + + if (IsExternalLoginOnly) + { + return await OnPostExternalLogin(ExternalProviders.First().AuthenticationScheme); + } + + return Page(); + } + + public async virtual Task OnPostAsync(string action) { await CheckLocalLoginAsync(); @@ -66,44 +120,17 @@ public class LoginModel : Volo.Abp.Account.Web.Pages.Account.LoginModel if (result.IsLockedOut) { - Alerts.Warning(L["UserLockedOutMessage"]); - return Page(); + return await HandleUserLockedOut(); } if (result.IsNotAllowed) { - var notAllowedUser = await GetIdentityUserAsync(LoginInput.UserNameOrEmailAddress); - if (await UserManager.CheckPasswordAsync(notAllowedUser, LoginInput.Password)) - { - // û޸ - if (notAllowedUser.ShouldChangePasswordOnNextLogin || await UserManager.ShouldPeriodicallyChangePasswordAsync(notAllowedUser)) - { - var changePwdIdentity = new ClaimsIdentity(AbpAccountAuthenticationTypes.ShouldChangePassword); - changePwdIdentity.AddClaim(new Claim(AbpClaimTypes.UserId, notAllowedUser.Id.ToString())); - if (notAllowedUser.TenantId.HasValue) - { - changePwdIdentity.AddClaim(new Claim(AbpClaimTypes.TenantId, notAllowedUser.TenantId.ToString())); - } - - await HttpContext.SignInAsync(AbpAccountAuthenticationTypes.ShouldChangePassword, new ClaimsPrincipal(changePwdIdentity)); - - return RedirectToPage("ChangePassword", new - { - returnUrl = ReturnUrl, - returnUrlHash = ReturnUrlHash, - rememberMe = LoginInput.RememberMe - }); - } - } - - Alerts.Warning(L["LoginIsNotAllowed"]); - return Page(); + return await HandleUserNotAllowed(); } if (!result.Succeeded) { - Alerts.Danger(L["InvalidUserNameOrPassword"]); - return Page(); + return await HandleUserNameOrPasswordInvalid(); } //TODO: Find a way of getting user's id from the logged in user and do not query it again like that! @@ -117,7 +144,123 @@ public class LoginModel : Volo.Abp.Account.Web.Pages.Account.LoginModel return await RedirectSafelyAsync(ReturnUrl, ReturnUrlHash); } - protected override Task TwoFactorLoginResultAsync() + public virtual async Task OnPostExternalLogin(string provider) + { + var redirectUrl = Url.Page("./Login", pageHandler: "ExternalLoginCallback", values: new { ReturnUrl, ReturnUrlHash }); + var properties = SignInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); + properties.Items["scheme"] = provider; + + return await Task.FromResult(Challenge(properties, provider)); + } + + 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"); + } + + await IdentityOptions.SetAsync(); + + 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.Succeeded) + { + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() + { + Identity = IdentitySecurityLogIdentityConsts.IdentityExternal, + Action = "Login" + result + }); + } + + if (result.IsLockedOut) + { + Logger.LogWarning($"External login callback error: user is locked out!"); + throw new UserFriendlyException("Cannot proceed because user is locked out!"); + } + + if (result.IsNotAllowed) + { + Logger.LogWarning($"External login callback error: user is not allowed!"); + throw new UserFriendlyException("Cannot proceed because user is not allowed!"); + } + + IdentityUser user; + if (result.Succeeded) + { + user = await UserManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey); + if (user != null) + { + // Clear the dynamic claims cache. + await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId); + } + + return await RedirectSafelyAsync(returnUrl, returnUrlHash); + } + + //TODO: Handle other cases for result! + + var email = loginInfo.Principal.FindFirstValue(AbpClaimTypes.Email) ?? loginInfo.Principal.FindFirstValue(ClaimTypes.Email); + if (email.IsNullOrWhiteSpace()) + { + return RedirectToPage("./Register", new + { + IsExternalLogin = true, + ExternalLoginAuthSchema = loginInfo.LoginProvider, + ReturnUrl = returnUrl + }); + } + + user = await UserManager.FindByEmailAsync(email); + if (user == null) + { + return RedirectToPage("./Register", new + { + IsExternalLogin = true, + ExternalLoginAuthSchema = loginInfo.LoginProvider, + ReturnUrl = returnUrl + }); + } + + if (await UserManager.FindByLoginAsync(loginInfo.LoginProvider, loginInfo.ProviderKey) == null) + { + CheckIdentityErrors(await UserManager.AddLoginAsync(user, loginInfo)); + } + + await SignInManager.SignInAsync(user, false); + + await IdentitySecurityLogManager.SaveAsync(new IdentitySecurityLogContext() + { + Identity = IdentitySecurityLogIdentityConsts.IdentityExternal, + Action = result.ToIdentitySecurityLogAction(), + UserName = user.Name + }); + + // Clear the dynamic claims cache. + await IdentityDynamicClaimsPrincipalContributorCache.ClearAsync(user.Id, user.TenantId); + + return await RedirectSafelyAsync(returnUrl, returnUrlHash); + } + + protected virtual Task TwoFactorLoginResultAsync() { // ض˫֤ҳ return Task.FromResult(RedirectToPage("SendCode", new @@ -128,39 +271,117 @@ public class LoginModel : Volo.Abp.Account.Web.Pages.Account.LoginModel })); } + protected virtual async Task GetIdentityUserAsync(string userNameOrEmailAddress) { return await UserManager.FindByNameAsync(LoginInput.UserNameOrEmailAddress) ?? await UserManager.FindByEmailAsync(LoginInput.UserNameOrEmailAddress); } - protected async override Task> GetExternalProviders() + protected async virtual Task> GetExternalProviders() { var schemes = await SchemeProvider.GetAllSchemesAsync(); + var externalProviders = await ExternalProviderService.GetAllAsync(); - 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(); - - foreach (var provider in providers) + var externalProviderModels = new List(); + foreach (var scheme in schemes) { - var localizedDisplayName = L[provider.DisplayName]; - if (localizedDisplayName.ResourceNotFound) + if (TryGetExternalLoginProvider(scheme, externalProviders, out var externalLoginProvider) || + scheme.Name.Equals(AccountOptions.WindowsAuthenticationSchemeName, StringComparison.OrdinalIgnoreCase)) { - localizedDisplayName = L["AuthenticationScheme:" + provider.DisplayName]; + externalProviderModels.Add(new ExternalLoginProviderModel + { + Name = externalLoginProvider.Name, + AuthenticationScheme = scheme.Name, + DisplayName = externalLoginProvider.DisplayName, + ComponentType = externalLoginProvider.ComponentType, + }); } + } - if (!localizedDisplayName.ResourceNotFound) + return externalProviderModels; + } + + protected virtual bool TryGetExternalLoginProvider(AuthenticationScheme scheme, List externalProviders, out ExternalLoginProviderModel externalLoginProvider) + { + if (ReflectionHelper.IsAssignableToGenericType(scheme.HandlerType, typeof(RemoteAuthenticationHandler<>))) + { + externalLoginProvider = externalProviders.FirstOrDefault(x => x.Name == scheme.Name); + return externalLoginProvider != null; + } + + externalLoginProvider = null; + return false; + } + + protected virtual async Task ReplaceEmailToUsernameOfInputIfNeeds() + { + if (!ValidationHelper.IsValidEmailAddress(LoginInput.UserNameOrEmailAddress)) + { + return; + } + + var userByUsername = await UserManager.FindByNameAsync(LoginInput.UserNameOrEmailAddress); + if (userByUsername != null) + { + return; + } + + var userByEmail = await UserManager.FindByEmailAsync(LoginInput.UserNameOrEmailAddress); + if (userByEmail == null) + { + return; + } + + LoginInput.UserNameOrEmailAddress = userByEmail.UserName; + } + + protected virtual async Task CheckLocalLoginAsync() + { + if (!await SettingProvider.IsTrueAsync(AccountSettingNames.EnableLocalLogin)) + { + throw new UserFriendlyException(L["LocalLoginDisabledMessage"]); + } + } + + protected virtual Task HandleUserLockedOut() + { + Alerts.Warning(L["UserLockedOutMessage"]); + return Task.FromResult(Page()); + } + + protected async virtual Task HandleUserNotAllowed() + { + var notAllowedUser = await GetIdentityUserAsync(LoginInput.UserNameOrEmailAddress); + if (await UserManager.CheckPasswordAsync(notAllowedUser, LoginInput.Password)) + { + // û޸ + if (notAllowedUser.ShouldChangePasswordOnNextLogin || await UserManager.ShouldPeriodicallyChangePasswordAsync(notAllowedUser)) { - provider.DisplayName = localizedDisplayName.Value; + var changePwdIdentity = new ClaimsIdentity(AbpAccountAuthenticationTypes.ShouldChangePassword); + changePwdIdentity.AddClaim(new Claim(AbpClaimTypes.UserId, notAllowedUser.Id.ToString())); + if (notAllowedUser.TenantId.HasValue) + { + changePwdIdentity.AddClaim(new Claim(AbpClaimTypes.TenantId, notAllowedUser.TenantId.ToString())); + } + + await HttpContext.SignInAsync(AbpAccountAuthenticationTypes.ShouldChangePassword, new ClaimsPrincipal(changePwdIdentity)); + + return RedirectToPage("ChangePassword", new + { + returnUrl = ReturnUrl, + returnUrlHash = ReturnUrlHash, + rememberMe = LoginInput.RememberMe + }); } } + Alerts.Warning(L["LoginIsNotAllowed"]); + return Page(); + } - return providers; + protected virtual Task HandleUserNameOrPasswordInvalid() + { + Alerts.Danger(L["InvalidUserNameOrPassword"]); + return Task.FromResult(Page()); } } diff --git a/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.WeChat/LINGYUN/Abp/OpenIddict/WeChat/WeChatOffcialTokenExtensionGrant.cs b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.WeChat/LINGYUN/Abp/OpenIddict/WeChat/WeChatOffcialTokenExtensionGrant.cs index a802e5624..2a9290767 100644 --- a/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.WeChat/LINGYUN/Abp/OpenIddict/WeChat/WeChatOffcialTokenExtensionGrant.cs +++ b/aspnet-core/modules/openIddict/LINGYUN.Abp.OpenIddict.WeChat/LINGYUN/Abp/OpenIddict/WeChat/WeChatOffcialTokenExtensionGrant.cs @@ -1,12 +1,7 @@ using LINGYUN.Abp.WeChat.Official; -using LINGYUN.Abp.WeChat.Official.Features; using LINGYUN.Abp.WeChat.OpenId; -using Microsoft.Extensions.Localization; using System.Threading.Tasks; -using Volo.Abp; -using Volo.Abp.Features; using Volo.Abp.OpenIddict.ExtensionGrantTypes; -using Volo.Abp.OpenIddict.Localization; namespace LINGYUN.Abp.OpenIddict.WeChat; public class WeChatOffcialTokenExtensionGrant : WeChatTokenExtensionGrant @@ -17,17 +12,17 @@ public class WeChatOffcialTokenExtensionGrant : WeChatTokenExtensionGrant public override string AuthenticationMethod => AbpWeChatOfficialConsts.AuthenticationMethod; - protected async override Task CheckFeatureAsync(ExtensionGrantContext context) - { - var featureChecker = GetRequiredService(context); + //protected async override Task CheckFeatureAsync(ExtensionGrantContext context) + //{ + // var featureChecker = GetRequiredService(context); - if (!await featureChecker.IsEnabledAsync(WeChatOfficialFeatures.EnableAuthorization)) - { - var localizer = GetRequiredService>(context); + // if (!await featureChecker.IsEnabledAsync(WeChatOfficialFeatures.EnableAuthorization)) + // { + // var localizer = GetRequiredService>(context); - throw new AbpException(localizer["OfficialAuthorizationDisabledMessage"]); - } - } + // throw new AbpException(localizer["OfficialAuthorizationDisabledMessage"]); + // } + //} protected async override Task FindOpenIdAsync(ExtensionGrantContext context, string code) { diff --git a/aspnet-core/modules/settings/LINGYUN.Abp.SettingManagement.Application/LINGYUN.Abp.SettingManagement.Application.csproj b/aspnet-core/modules/settings/LINGYUN.Abp.SettingManagement.Application/LINGYUN.Abp.SettingManagement.Application.csproj index 7bbf2f072..14ff0c1fb 100644 --- a/aspnet-core/modules/settings/LINGYUN.Abp.SettingManagement.Application/LINGYUN.Abp.SettingManagement.Application.csproj +++ b/aspnet-core/modules/settings/LINGYUN.Abp.SettingManagement.Application/LINGYUN.Abp.SettingManagement.Application.csproj @@ -28,6 +28,7 @@ + diff --git a/aspnet-core/modules/settings/LINGYUN.Abp.SettingManagement.Application/LINGYUN/Abp/SettingManagement/AbpSettingManagementApplicationModule.cs b/aspnet-core/modules/settings/LINGYUN.Abp.SettingManagement.Application/LINGYUN/Abp/SettingManagement/AbpSettingManagementApplicationModule.cs index 0794787ed..948f517ef 100644 --- a/aspnet-core/modules/settings/LINGYUN.Abp.SettingManagement.Application/LINGYUN/Abp/SettingManagement/AbpSettingManagementApplicationModule.cs +++ b/aspnet-core/modules/settings/LINGYUN.Abp.SettingManagement.Application/LINGYUN/Abp/SettingManagement/AbpSettingManagementApplicationModule.cs @@ -1,4 +1,7 @@ -using Microsoft.Extensions.DependencyInjection; +using LINGYUN.Abp.Account; +using LINGYUN.Abp.Account.OAuth; +using LINGYUN.Abp.Account.OAuth.Localization; +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Application; using Volo.Abp.Localization; using Volo.Abp.Modularity; @@ -13,6 +16,8 @@ namespace LINGYUN.Abp.SettingManagement; typeof(AbpSettingManagementDomainModule), typeof(AbpSettingManagementApplicationContractsModule), typeof(VoloAbpSettingManagementApplicationContractsModule), + typeof(AbpAccountApplicationContractsModule), + typeof(AbpAccountOAuthModule), typeof(AbpDddApplicationModule) )] public class AbpSettingManagementApplicationModule : AbpModule @@ -29,7 +34,8 @@ public class AbpSettingManagementApplicationModule : AbpModule Configure(options => { options.Resources.Get() - .AddVirtualJson("/LINGYUN/Abp/SettingManagement/Localization/Resources"); + .AddVirtualJson("/LINGYUN/Abp/SettingManagement/Localization/Resources") + .AddBaseTypes(typeof(AccountOAuthResource)); }); } } diff --git a/aspnet-core/modules/settings/LINGYUN.Abp.SettingManagement.Application/LINGYUN/Abp/SettingManagement/SettingAppService.cs b/aspnet-core/modules/settings/LINGYUN.Abp.SettingManagement.Application/LINGYUN/Abp/SettingManagement/SettingAppService.cs index 3c3a4b019..7a40bc1ac 100644 --- a/aspnet-core/modules/settings/LINGYUN.Abp.SettingManagement.Application/LINGYUN/Abp/SettingManagement/SettingAppService.cs +++ b/aspnet-core/modules/settings/LINGYUN.Abp.SettingManagement.Application/LINGYUN/Abp/SettingManagement/SettingAppService.cs @@ -1,4 +1,6 @@ -using LINGYUN.Abp.Identity; +using LINGYUN.Abp.Account.OAuth.Features; +using LINGYUN.Abp.Account.OAuth.Settings; +using LINGYUN.Abp.Identity; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Options; using System.Linq; @@ -489,6 +491,48 @@ public class SettingAppService : ApplicationService, ISettingAppService, ISettin #endregion + #region 外部登录 + + var oauthSettingDto = new SettingGroupDto(L["Settings:ExternalOAuthLogin"], L["Settings:ExternalOAuthLogin"]); + + if (await FeatureChecker.IsEnabledAsync(true, AccountOAuthFeatureNames.GitHub.Enable)) + { + var githubOAuthSetting = oauthSettingDto.AddSetting(L["Settings:GitHubAuth"], L["Settings:GitHubAuth"]); + githubOAuthSetting.AddDetail( + await SettingDefinitionManager.GetAsync(AccountOAuthSettingNames.GitHub.ClientId), + StringLocalizerFactory, + await SettingManager.GetOrNullAsync(AccountOAuthSettingNames.GitHub.ClientId, providerName, providerKey), + ValueType.String, + providerName); + githubOAuthSetting.AddDetail( + await SettingDefinitionManager.GetAsync(AccountOAuthSettingNames.GitHub.ClientSecret), + StringLocalizerFactory, + await SettingManager.GetOrNullAsync(AccountOAuthSettingNames.GitHub.ClientSecret, providerName, providerKey), + ValueType.String, + providerName); + } + + if (await FeatureChecker.IsEnabledAsync(true, AccountOAuthFeatureNames.Bilibili.Enable)) + { + var bilibiliOAuthSetting = oauthSettingDto.AddSetting(L["Settings:BilibiliAuth"], L["Settings:BilibiliAuth"]); + bilibiliOAuthSetting.AddDetail( + await SettingDefinitionManager.GetAsync(AccountOAuthSettingNames.Bilibili.ClientId), + StringLocalizerFactory, + await SettingManager.GetOrNullAsync(AccountOAuthSettingNames.Bilibili.ClientId, providerName, providerKey), + ValueType.String, + providerName); + bilibiliOAuthSetting.AddDetail( + await SettingDefinitionManager.GetAsync(AccountOAuthSettingNames.Bilibili.ClientSecret), + StringLocalizerFactory, + await SettingManager.GetOrNullAsync(AccountOAuthSettingNames.Bilibili.ClientSecret, providerName, providerKey), + ValueType.String, + providerName); + } + + settingGroups.AddGroup(oauthSettingDto); + + #endregion + return settingGroups; }