Browse Source
* Add OpenIddict management module. * Add an identity server that uses OpenIddict. * Add the identity server API that uses OpenIddict. * LINGYUN.Abp.IdentityServer.WeChat module renamed LINGYUN.Abp.Authentication.WeChat. * LINGYUN.Abp.IdentityServer.QQ module renamed LINGYUN.Abp.Authentication.QQ. * Update Directory.Build.props. * Update common.props.pull/712/head
657 changed files with 130623 additions and 400 deletions
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,19 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.props" /> |
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="$(MicrosoftPackageVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\cloud-tencent\LINGYUN.Abp.Tencent.QQ\LINGYUN.Abp.Tencent.QQ.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,8 @@ |
|||
namespace LINGYUN.Abp.Authentication.QQ; |
|||
|
|||
public static class AbpAuthenticationQQConsts |
|||
{ |
|||
public static string AuthenticationScheme { get; set; } = "QQ Connect"; |
|||
public static string DisplayName { get; set; } = "QQ Connect"; |
|||
public static string CallbackPath { get; set; } = "/signin-callback"; |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using LINGYUN.Abp.Tencent.QQ; |
|||
using Microsoft.AspNetCore.Authentication; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.Authentication.QQ; |
|||
|
|||
[DependsOn(typeof(AbpTencentQQModule))] |
|||
public class AbpAuthenticationQQModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services |
|||
.AddAuthentication() |
|||
.AddQQConnect(); |
|||
} |
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
namespace LINGYUN.Abp.Authentication.QQ |
|||
{ |
|||
/// <summary>
|
|||
/// QQ互联身份类型,可以像 <see cref="Volo.Abp.Security.Claims.AbpClaimTypes"/> 自行配置
|
|||
/// <br />
|
|||
/// See: <see cref="https://wiki.connect.qq.com/get_user_info"/>
|
|||
/// </summary>
|
|||
public class AbpQQClaimTypes |
|||
{ |
|||
/// <summary>
|
|||
/// 用户的唯一标识
|
|||
/// </summary>
|
|||
public static string OpenId { get; set; } = "qq-openid"; // 可变更
|
|||
/// <summary>
|
|||
/// 用户昵称
|
|||
/// </summary>
|
|||
public static string NickName { get; set; } = "nickname"; |
|||
/// <summary>
|
|||
/// 性别。 如果获取不到则默认返回"男"
|
|||
/// </summary>
|
|||
public static string Gender { get; set; } = "gender"; |
|||
/// <summary>
|
|||
/// 用户头像, 取自字段: figureurl_qq_1
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// 根据QQ互联文档, 40x40的头像是一定会存在的, 只取40x40的头像
|
|||
/// see: https://wiki.connect.qq.com/get_user_info
|
|||
/// </remarks>
|
|||
public static string AvatarUrl { get; set; } = "avatar"; |
|||
} |
|||
} |
|||
@ -0,0 +1,176 @@ |
|||
using LINGYUN.Abp.Authentication.QQ; |
|||
using LINGYUN.Abp.Tencent.QQ; |
|||
using Microsoft.AspNetCore.Authentication.OAuth; |
|||
using Microsoft.AspNetCore.WebUtilities; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Options; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Net.Http; |
|||
using System.Security.Claims; |
|||
using System.Text.Encodings.Web; |
|||
using System.Text.Json; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Microsoft.AspNetCore.Authentication.QQ |
|||
{ |
|||
/// <summary>
|
|||
/// QQ互联实现
|
|||
/// </summary>
|
|||
public class QQConnectOAuthHandler : OAuthHandler<QQConnectOAuthOptions> |
|||
{ |
|||
protected AbpTencentQQOptionsFactory TencentQQOptionsFactory { get; } |
|||
public QQConnectOAuthHandler( |
|||
IOptionsMonitor<QQConnectOAuthOptions> options, |
|||
AbpTencentQQOptionsFactory tencentQQOptionsFactory, |
|||
ILoggerFactory logger, |
|||
UrlEncoder encoder, |
|||
ISystemClock clock) |
|||
: base(options, logger, encoder, clock) |
|||
{ |
|||
TencentQQOptionsFactory = tencentQQOptionsFactory; |
|||
} |
|||
|
|||
protected override async Task InitializeHandlerAsync() |
|||
{ |
|||
var options = await TencentQQOptionsFactory.CreateAsync(); |
|||
|
|||
// 用配置项重写
|
|||
Options.ClientId = options.AppId; |
|||
Options.ClientSecret = options.AppKey; |
|||
Options.IsMobile = options.IsMobile; |
|||
|
|||
await base.InitializeHandlerAsync(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 构建用户授权地址
|
|||
/// </summary>
|
|||
protected override string BuildChallengeUrl(AuthenticationProperties properties, string redirectUri) |
|||
{ |
|||
var challengeUrl = base.BuildChallengeUrl(properties, redirectUri); |
|||
if (Options.IsMobile) |
|||
{ |
|||
challengeUrl += "&display=mobile"; |
|||
} |
|||
return challengeUrl; |
|||
|
|||
} |
|||
|
|||
/// <summary>
|
|||
/// code换取access_token
|
|||
/// </summary>
|
|||
protected override async Task<OAuthTokenResponse> ExchangeCodeAsync(OAuthCodeExchangeContext context) |
|||
{ |
|||
var address = QueryHelpers.AddQueryString(Options.TokenEndpoint, new Dictionary<string, string>() |
|||
{ |
|||
{ "client_id", Options.ClientId }, |
|||
{ "redirect_uri", context.RedirectUri }, |
|||
{ "client_secret", Options.ClientSecret}, |
|||
{ "code", context.Code}, |
|||
{ "grant_type","authorization_code"} |
|||
}); |
|||
|
|||
var response = await Backchannel.GetAsync(address); |
|||
if (!response.IsSuccessStatusCode) |
|||
{ |
|||
Logger.LogError("An error occurred while retrieving an access token: the remote server " + |
|||
"returned a {Status} response with the following payload: {Headers} {Body}.", |
|||
/* Status: */ response.StatusCode, |
|||
/* Headers: */ response.Headers.ToString(), |
|||
/* Body: */ await response.Content.ReadAsStringAsync()); |
|||
|
|||
return OAuthTokenResponse.Failed(new Exception("An error occurred while retrieving an access token.")); |
|||
} |
|||
|
|||
var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); |
|||
if (!string.IsNullOrEmpty(payload.GetRootString("errcode"))) |
|||
{ |
|||
Logger.LogError("An error occurred while retrieving an access token: the remote server " + |
|||
"returned a {Status} response with the following payload: {Headers} {Body}.", |
|||
/* Status: */ response.StatusCode, |
|||
/* Headers: */ response.Headers.ToString(), |
|||
/* Body: */ await response.Content.ReadAsStringAsync()); |
|||
|
|||
return OAuthTokenResponse.Failed(new Exception("An error occurred while retrieving an access token.")); |
|||
} |
|||
return OAuthTokenResponse.Success(payload); |
|||
} |
|||
|
|||
protected override async Task<AuthenticationTicket> CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens) |
|||
{ |
|||
var openIdEndpoint = Options.OpenIdEndpoint + "?access_token=" + tokens.AccessToken + "&fmt=json"; |
|||
var openIdResponse = await Backchannel.GetAsync(openIdEndpoint, Context.RequestAborted); |
|||
openIdResponse.EnsureSuccessStatusCode(); |
|||
|
|||
var openIdPayload = JsonDocument.Parse(await openIdResponse.Content.ReadAsStringAsync()); |
|||
var openId = openIdPayload.GetRootString("openid"); |
|||
|
|||
identity.AddClaim(new Claim(AbpQQClaimTypes.OpenId, openId, ClaimValueTypes.String, Options.ClaimsIssuer)); |
|||
|
|||
var address = QueryHelpers.AddQueryString(Options.UserInformationEndpoint, new Dictionary<string, string> |
|||
{ |
|||
{"oauth_consumer_key", Options.ClientId}, |
|||
{"access_token", tokens.AccessToken}, |
|||
{"openid", openId} |
|||
}); |
|||
|
|||
var response = await Backchannel.GetAsync(address); |
|||
if (!response.IsSuccessStatusCode) |
|||
{ |
|||
Logger.LogError("An error occurred while retrieving the user profile: the remote server " + |
|||
"returned a {Status} response with the following payload: {Headers} {Body}.", |
|||
/* Status: */ response.StatusCode, |
|||
/* Headers: */ response.Headers.ToString(), |
|||
/* Body: */ await response.Content.ReadAsStringAsync()); |
|||
|
|||
throw new HttpRequestException("An error occurred while retrieving user information."); |
|||
} |
|||
|
|||
var userInfoPayload = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); |
|||
var errorCode = userInfoPayload.GetRootString("ret"); |
|||
if (!"0".Equals(errorCode)) |
|||
{ |
|||
// See: https://wiki.connect.qq.com/%e5%85%ac%e5%85%b1%e8%bf%94%e5%9b%9e%e7%a0%81%e8%af%b4%e6%98%8e
|
|||
Logger.LogError("An error occurred while retrieving the user profile: the remote server " + |
|||
"returned code {Code} response with message: {Message}.", |
|||
errorCode, |
|||
userInfoPayload.GetRootString("msg")); |
|||
|
|||
throw new HttpRequestException("An error occurred while retrieving user information."); |
|||
} |
|||
|
|||
var nickName = userInfoPayload.GetRootString("nickname"); |
|||
if (!nickName.IsNullOrWhiteSpace()) |
|||
{ |
|||
identity.AddClaim(new Claim(AbpQQClaimTypes.NickName, nickName, ClaimValueTypes.String, Options.ClaimsIssuer)); |
|||
} |
|||
var gender = userInfoPayload.GetRootString("gender"); |
|||
if (!gender.IsNullOrWhiteSpace()) |
|||
{ |
|||
identity.AddClaim(new Claim(AbpQQClaimTypes.Gender, gender, ClaimValueTypes.String, Options.ClaimsIssuer)); |
|||
} |
|||
var avatarUrl = userInfoPayload.GetRootString("figureurl_qq_1"); |
|||
if (!avatarUrl.IsNullOrWhiteSpace()) |
|||
{ |
|||
identity.AddClaim(new Claim(AbpQQClaimTypes.AvatarUrl, avatarUrl, ClaimValueTypes.String, Options.ClaimsIssuer)); |
|||
} |
|||
|
|||
var context = new OAuthCreatingTicketContext( |
|||
new ClaimsPrincipal(identity), |
|||
properties, |
|||
Context, |
|||
Scheme, |
|||
Options, |
|||
Backchannel, |
|||
tokens, |
|||
userInfoPayload.RootElement); |
|||
|
|||
context.RunClaimActions(); |
|||
|
|||
await Events.CreatingTicket(context); |
|||
|
|||
return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,46 @@ |
|||
using LINGYUN.Abp.Authentication.QQ; |
|||
using Microsoft.AspNetCore.Authentication.OAuth; |
|||
using Microsoft.AspNetCore.Http; |
|||
using System.Security.Claims; |
|||
|
|||
namespace Microsoft.AspNetCore.Authentication.QQ |
|||
{ |
|||
public class QQConnectOAuthOptions : OAuthOptions |
|||
{ |
|||
/// <summary>
|
|||
/// 是否移动端样式
|
|||
/// </summary>
|
|||
public bool IsMobile { get; set; } |
|||
/// <summary>
|
|||
/// 获取用户OpenID_OAuth2.0
|
|||
/// </summary>
|
|||
public string OpenIdEndpoint { get; set; } |
|||
|
|||
public QQConnectOAuthOptions() |
|||
{ |
|||
// 用于防止初始化错误,会在OAuthHandler.InitializeHandlerAsync中进行重写
|
|||
ClientId = "QQConnect"; |
|||
ClientSecret = "QQConnect"; |
|||
|
|||
ClaimsIssuer = "connect.qq.com"; |
|||
CallbackPath = new PathString(AbpAuthenticationQQConsts.CallbackPath); |
|||
|
|||
AuthorizationEndpoint = "https://graph.qq.com/oauth2.0/authorize"; |
|||
TokenEndpoint = "https://graph.qq.com/oauth2.0/token"; |
|||
OpenIdEndpoint = "https://graph.qq.com/oauth2.0/me"; |
|||
UserInformationEndpoint = "https://graph.qq.com/user/get_user_info"; |
|||
|
|||
Scope.Add("get_user_info"); |
|||
|
|||
// 这个原始的属性一定要写进去,框架关联判断是否绑定QQ
|
|||
ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "openid"); |
|||
ClaimActions.MapJsonKey(ClaimTypes.Name, "nickname"); |
|||
|
|||
// 把自定义的身份标识写进令牌
|
|||
ClaimActions.MapJsonKey(AbpQQClaimTypes.OpenId, "openid"); |
|||
ClaimActions.MapJsonKey(AbpQQClaimTypes.NickName, "nickname"); |
|||
ClaimActions.MapJsonKey(AbpQQClaimTypes.Gender, "gender"); |
|||
ClaimActions.MapJsonKey(AbpQQClaimTypes.AvatarUrl, "figureurl_qq_1"); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
using LINGYUN.Abp.Authentication.QQ; |
|||
using Microsoft.AspNetCore.Authentication.QQ; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using System; |
|||
|
|||
namespace Microsoft.AspNetCore.Authentication |
|||
{ |
|||
public static class QQAuthenticationExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// </summary>
|
|||
public static AuthenticationBuilder AddQQConnect( |
|||
this AuthenticationBuilder builder) |
|||
{ |
|||
return builder |
|||
.AddQQConnect( |
|||
AbpAuthenticationQQConsts.AuthenticationScheme, |
|||
AbpAuthenticationQQConsts.DisplayName, |
|||
options => { }); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// </summary>
|
|||
public static AuthenticationBuilder AddQQConnect( |
|||
this AuthenticationBuilder builder, |
|||
Action<QQConnectOAuthOptions> configureOptions) |
|||
{ |
|||
return builder |
|||
.AddQQConnect( |
|||
AbpAuthenticationQQConsts.AuthenticationScheme, |
|||
configureOptions); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// </summary>
|
|||
public static AuthenticationBuilder AddQQConnect( |
|||
this AuthenticationBuilder builder, |
|||
string authenticationScheme, |
|||
Action<QQConnectOAuthOptions> configureOptions) |
|||
{ |
|||
return builder |
|||
.AddQQConnect( |
|||
authenticationScheme, |
|||
AbpAuthenticationQQConsts.DisplayName, |
|||
configureOptions); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// </summary>
|
|||
public static AuthenticationBuilder AddQQConnect( |
|||
this AuthenticationBuilder builder, |
|||
string authenticationScheme, |
|||
string displayName, |
|||
Action<QQConnectOAuthOptions> configureOptions) |
|||
{ |
|||
return builder |
|||
.AddOAuth<QQConnectOAuthOptions, QQConnectOAuthHandler>( |
|||
authenticationScheme, |
|||
displayName, |
|||
configureOptions); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using System.Security.Cryptography; |
|||
|
|||
namespace System |
|||
{ |
|||
internal static class BytesExtensions |
|||
{ |
|||
public static byte[] Sha1(this byte[] data) |
|||
{ |
|||
using (var sha = SHA1.Create()) |
|||
{ |
|||
var hashBytes = sha.ComputeHash(data); |
|||
return hashBytes; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System.Security.Cryptography; |
|||
using System.Text; |
|||
|
|||
namespace System |
|||
{ |
|||
internal static class StringExtensions |
|||
{ |
|||
public static byte[] Sha1(this string str) |
|||
{ |
|||
using (var sha = SHA1.Create()) |
|||
{ |
|||
var hashBytes = sha.ComputeHash(Encoding.ASCII.GetBytes(str)); |
|||
return hashBytes; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace System.Text.Json |
|||
{ |
|||
internal static class JsonElementExtensions |
|||
{ |
|||
public static IEnumerable<string> GetRootStrings(this JsonDocument json, string key) |
|||
{ |
|||
return json.RootElement.GetStrings(key); |
|||
} |
|||
|
|||
public static IEnumerable<string> GetStrings(this JsonElement json, string key) |
|||
{ |
|||
var result = new List<string>(); |
|||
|
|||
if (json.TryGetProperty(key, out JsonElement property) && property.ValueKind == JsonValueKind.Array) |
|||
{ |
|||
foreach (var jsonProp in property.EnumerateArray()) |
|||
{ |
|||
result.Add(jsonProp.GetString()); |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
public static string GetRootString(this JsonDocument json, string key, string defaultValue = "") |
|||
{ |
|||
if (json.RootElement.TryGetProperty(key, out JsonElement property)) |
|||
{ |
|||
return property.GetString(); |
|||
} |
|||
return defaultValue; |
|||
} |
|||
|
|||
public static string GetString(this JsonElement json, string key, string defaultValue = "") |
|||
{ |
|||
if (json.TryGetProperty(key, out JsonElement property)) |
|||
{ |
|||
return property.GetString(); |
|||
} |
|||
return defaultValue; |
|||
} |
|||
|
|||
public static int GetRootInt32(this JsonDocument json, string key, int defaultValue = 0) |
|||
{ |
|||
if (json.RootElement.TryGetProperty(key, out JsonElement property) && property.TryGetInt32(out int value)) |
|||
{ |
|||
return value; |
|||
} |
|||
return defaultValue; |
|||
} |
|||
|
|||
public static int GetInt32(this JsonElement json, string key, int defaultValue = 0) |
|||
{ |
|||
if (json.TryGetProperty(key, out JsonElement property) && property.TryGetInt32(out int value)) |
|||
{ |
|||
return value; |
|||
} |
|||
return defaultValue; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,19 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.props" /> |
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="$(MicrosoftPackageVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\wechat\LINGYUN.Abp.WeChat.Official\LINGYUN.Abp.WeChat.Official.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,67 @@ |
|||
using LINGYUN.Abp.WeChat; |
|||
using LINGYUN.Abp.WeChat.Official; |
|||
|
|||
namespace LINGYUN.Abp.Authentication.WeChat; |
|||
|
|||
public static class AbpAuthenticationWeChatConsts |
|||
{ |
|||
/// <summary>
|
|||
/// 微信个人信息标识
|
|||
/// </summary>
|
|||
public static string ProfileKey => AbpWeChatGlobalConsts.ProfileKey; |
|||
/// <summary>
|
|||
/// 微信提供者标识
|
|||
/// </summary>
|
|||
public static string ProviderKey => AbpWeChatOfficialConsts.ProviderName; |
|||
/// <summary>
|
|||
/// 微信提供者显示名称
|
|||
/// </summary>
|
|||
public static string DisplayName => AbpWeChatGlobalConsts.DisplayName; |
|||
/// <summary>
|
|||
/// 回调地址
|
|||
/// </summary>
|
|||
public static string CallbackPath { get; set; } = "/signin-callback"; |
|||
|
|||
/// <summary>
|
|||
/// 微信客户端外的网页登录
|
|||
/// </summary>
|
|||
public const string QrConnectEndpoint = "https://open.weixin.qq.com/connect/qrconnect"; |
|||
|
|||
/// <summary>
|
|||
/// 微信客户端内的网页登录
|
|||
/// </summary>
|
|||
public const string AuthorizationEndpoint = "https://open.weixin.qq.com/connect/oauth2/authorize"; |
|||
|
|||
/// <summary>
|
|||
/// 用户允许授权后通过返回的code换取access_token地址
|
|||
/// </summary>
|
|||
public const string TokenEndpoint = "https://api.weixin.qq.com/sns/oauth2/access_token"; |
|||
|
|||
/// <summary>
|
|||
/// 使用access_token获取用户个人信息地址
|
|||
/// </summary>
|
|||
public const string UserInformationEndpoint = "https://api.weixin.qq.com/sns/userinfo"; |
|||
/// <summary>
|
|||
/// 弹出授权页面,可通过openid拿到昵称、性别、所在地。
|
|||
/// 并且, 即使在未关注的情况下,只要用户授权,也能获取其信息
|
|||
/// <br />
|
|||
/// <br />
|
|||
/// 详询: <see cref="https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html"/>
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// 以snsapi_userinfo为scope发起的网页授权,是用来获取用户的基本信息的。
|
|||
/// 但这种授权需要用户手动同意,并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息
|
|||
/// </remarks>
|
|||
public const string UserInfoScope = "snsapi_userinfo"; |
|||
/// <summary>
|
|||
/// 不弹出授权页面,直接跳转,只能获取用户openid
|
|||
/// <br />
|
|||
/// <br />
|
|||
/// 详询: <see cref="https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/Wechat_webpage_authorization.html"/>
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// 以snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,并且是静默授权并自动跳转到回调页的。
|
|||
/// 用户感知的就是直接进入了回调页(往往是业务页面)
|
|||
/// </remarks>
|
|||
public const string LoginScope = "snsapi_login"; |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using LINGYUN.Abp.WeChat.Official; |
|||
using Microsoft.AspNetCore.Authentication; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.Authentication.WeChat; |
|||
|
|||
[DependsOn(typeof(AbpWeChatOfficialModule))] |
|||
public class AbpAuthenticationWeChatModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services |
|||
.AddAuthentication() |
|||
.AddWeChat(); |
|||
} |
|||
} |
|||
@ -0,0 +1,336 @@ |
|||
using LINGYUN.Abp.Authentication.WeChat; |
|||
using LINGYUN.Abp.WeChat.Official; |
|||
using Microsoft.AspNetCore.Authentication.OAuth; |
|||
using Microsoft.AspNetCore.WebUtilities; |
|||
using Microsoft.Extensions.Caching.Distributed; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Options; |
|||
using Microsoft.Extensions.Primitives; |
|||
using Microsoft.Net.Http.Headers; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using System.Net.Http; |
|||
using System.Security.Claims; |
|||
using System.Text; |
|||
using System.Text.Encodings.Web; |
|||
using System.Text.Json; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Caching; |
|||
|
|||
namespace Microsoft.AspNetCore.Authentication.WeChat.Official |
|||
{ |
|||
/// <summary>
|
|||
/// 网页授权只有公众平台的实现
|
|||
/// </summary>
|
|||
public class WeChatOfficialOAuthHandler : OAuthHandler<WeChatOfficialOAuthOptions> |
|||
{ |
|||
protected IDistributedCache<WeChatOfficialStateCacheItem> Cache { get; } |
|||
protected AbpWeChatOfficialOptionsFactory WeChatOfficialOptionsFactory { get; } |
|||
public WeChatOfficialOAuthHandler( |
|||
IDistributedCache<WeChatOfficialStateCacheItem> cache, |
|||
IOptionsMonitor<WeChatOfficialOAuthOptions> options, |
|||
AbpWeChatOfficialOptionsFactory weChatOfficialOptionsFactory, |
|||
ILoggerFactory logger, |
|||
UrlEncoder encoder, |
|||
ISystemClock clock) |
|||
: base(options, logger, encoder, clock) |
|||
{ |
|||
Cache = cache; |
|||
WeChatOfficialOptionsFactory = weChatOfficialOptionsFactory; |
|||
} |
|||
|
|||
protected override async Task InitializeHandlerAsync() |
|||
{ |
|||
var weChatOfficialOptions = await WeChatOfficialOptionsFactory.CreateAsync(); |
|||
|
|||
// 用配置项重写
|
|||
Options.ClientId = weChatOfficialOptions.AppId; |
|||
Options.ClientSecret = weChatOfficialOptions.AppSecret; |
|||
|
|||
await base.InitializeHandlerAsync(); |
|||
} |
|||
|
|||
protected override async Task<AuthenticationTicket> CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens) |
|||
{ |
|||
var address = QueryHelpers.AddQueryString(Options.UserInformationEndpoint, new Dictionary<string, string> |
|||
{ |
|||
["access_token"] = tokens.AccessToken, |
|||
["openid"] = tokens.Response.GetRootString("openid") |
|||
}); |
|||
|
|||
var response = await Backchannel.GetAsync(address); |
|||
if (!response.IsSuccessStatusCode) |
|||
{ |
|||
Logger.LogError("An error occurred while retrieving the user profile: the remote server " + |
|||
"returned a {Status} response with the following payload: {Headers} {Body}.", |
|||
/* Status: */ response.StatusCode, |
|||
/* Headers: */ response.Headers.ToString(), |
|||
/* Body: */ await response.Content.ReadAsStringAsync()); |
|||
|
|||
throw new HttpRequestException("An error occurred while retrieving user information."); |
|||
} |
|||
|
|||
var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); |
|||
if (!string.IsNullOrEmpty(payload.GetRootString("errcode"))) |
|||
{ |
|||
Logger.LogError("An error occurred while retrieving the user profile: the remote server " + |
|||
"returned a {Status} response with the following payload: {Headers} {Body}.", |
|||
/* Status: */ response.StatusCode, |
|||
/* Headers: */ response.Headers.ToString(), |
|||
/* Body: */ await response.Content.ReadAsStringAsync()); |
|||
|
|||
throw new HttpRequestException("An error occurred while retrieving user information."); |
|||
} |
|||
|
|||
var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Scheme, Options, Backchannel, tokens, payload.RootElement); |
|||
context.RunClaimActions(); |
|||
|
|||
await Events.CreatingTicket(context); |
|||
|
|||
// TODO: 此处通过唯一的 CorrelationId, 将 properties生成的State缓存删除
|
|||
var state = Request.Query["state"]; |
|||
|
|||
var stateCacheKey = WeChatOfficialStateCacheItem.CalculateCacheKey(state.ToString().ToMd5(), null); |
|||
await Cache.RemoveAsync(stateCacheKey, token: Context.RequestAborted); |
|||
|
|||
return new AuthenticationTicket(context.Principal, context.Properties, Scheme.Name); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// code换取access_token
|
|||
/// </summary>
|
|||
protected override async Task<OAuthTokenResponse> ExchangeCodeAsync(OAuthCodeExchangeContext context) |
|||
{ |
|||
var address = QueryHelpers.AddQueryString(Options.TokenEndpoint, new Dictionary<string, string>() |
|||
{ |
|||
["appid"] = Options.ClientId, |
|||
["secret"] = Options.ClientSecret, |
|||
["code"] = context.Code, |
|||
["grant_type"] = "authorization_code" |
|||
}); |
|||
|
|||
var response = await Backchannel.GetAsync(address); |
|||
if (!response.IsSuccessStatusCode) |
|||
{ |
|||
Logger.LogError("An error occurred while retrieving an access token: the remote server " + |
|||
"returned a {Status} response with the following payload: {Headers} {Body}.", |
|||
/* Status: */ response.StatusCode, |
|||
/* Headers: */ response.Headers.ToString(), |
|||
/* Body: */ await response.Content.ReadAsStringAsync()); |
|||
|
|||
return OAuthTokenResponse.Failed(new Exception("An error occurred while retrieving an access token.")); |
|||
} |
|||
|
|||
var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync()); |
|||
if (!string.IsNullOrEmpty(payload.GetRootString("errcode"))) |
|||
{ |
|||
Logger.LogError("An error occurred while retrieving an access token: the remote server " + |
|||
"returned a {Status} response with the following payload: {Headers} {Body}.", |
|||
/* Status: */ response.StatusCode, |
|||
/* Headers: */ response.Headers.ToString(), |
|||
/* Body: */ await response.Content.ReadAsStringAsync()); |
|||
|
|||
return OAuthTokenResponse.Failed(new Exception("An error occurred while retrieving an access token.")); |
|||
} |
|||
return OAuthTokenResponse.Success(payload); |
|||
} |
|||
|
|||
protected override async Task HandleChallengeAsync(AuthenticationProperties properties) |
|||
{ |
|||
await base.HandleChallengeAsync(properties); |
|||
|
|||
// TODO: 此处已经生成唯一的 CorrelationId, 可以借此将 properties生成State之后再进行缓存
|
|||
// 注: 默认的State对于微信来说太长(微信只支持128位长度的State),因此巧妙的利用CorrelationId的MD5值来替代State
|
|||
// MD5转换防止直接通过CorrelationId干些别的事情...
|
|||
var state = properties.Items[".xsrf"]; |
|||
|
|||
var stateToken = Options.StateDataFormat.Protect(properties); |
|||
var stateCacheKey = WeChatOfficialStateCacheItem.CalculateCacheKey(state.ToMd5(), null); |
|||
|
|||
await Cache |
|||
.SetAsync( |
|||
stateCacheKey, |
|||
new WeChatOfficialStateCacheItem(stateToken), |
|||
new DistributedCacheEntryOptions |
|||
{ |
|||
AbsoluteExpiration = Clock.UtcNow.AddMinutes(2) // TODO: 设定2分钟过期?
|
|||
}, |
|||
token: Context.RequestAborted); |
|||
} |
|||
/// <summary>
|
|||
/// 构建用户授权地址
|
|||
/// </summary>
|
|||
protected override string BuildChallengeUrl(AuthenticationProperties properties, string redirectUri) |
|||
{ |
|||
var state = properties.Items[".xsrf"]; |
|||
|
|||
var isWeChatBrewserRequest = IsWeChatBrowser(); |
|||
|
|||
var scope = isWeChatBrewserRequest |
|||
? AbpAuthenticationWeChatConsts.UserInfoScope |
|||
: FormatScope(); |
|||
|
|||
var endPoint = isWeChatBrewserRequest |
|||
? Options.AuthorizationEndpoint |
|||
: AbpAuthenticationWeChatConsts.QrConnectEndpoint; |
|||
|
|||
var challengeUrl = QueryHelpers.AddQueryString(endPoint, new Dictionary<string, string> |
|||
{ |
|||
["appid"] = Options.ClientId, |
|||
["redirect_uri"] = redirectUri, |
|||
["response_type"] = "code" |
|||
}); |
|||
|
|||
challengeUrl += $"&scope={scope}&state={state.ToMd5()}"; |
|||
|
|||
return challengeUrl; |
|||
} |
|||
|
|||
protected override async Task<HandleRequestResult> HandleRemoteAuthenticateAsync() |
|||
{ |
|||
var query = Request.Query; |
|||
|
|||
// TODO: 此处借用唯一的 CorrelationId, 将 properties生成的State缓存取出,进行解密
|
|||
var state = query["state"]; |
|||
|
|||
var stateCacheKey = WeChatOfficialStateCacheItem.CalculateCacheKey(state.ToString().ToMd5(), null); |
|||
var stateCacheItem = await Cache.GetAsync(stateCacheKey, token: Context.RequestAborted); |
|||
|
|||
var properties = Options.StateDataFormat.Unprotect(stateCacheItem.State); |
|||
|
|||
if (properties == null) |
|||
{ |
|||
return HandleRequestResult.Fail("The oauth state was missing or invalid."); |
|||
} |
|||
|
|||
// OAuth2 10.12 CSRF
|
|||
if (!ValidateCorrelationId(properties)) |
|||
{ |
|||
return HandleRequestResult.Fail("Correlation failed.", properties); |
|||
} |
|||
|
|||
var error = query["error"]; |
|||
if (!StringValues.IsNullOrEmpty(error)) |
|||
{ |
|||
// Note: access_denied errors are special protocol errors indicating the user didn't
|
|||
// approve the authorization demand requested by the remote authorization server.
|
|||
// Since it's a frequent scenario (that is not caused by incorrect configuration),
|
|||
// denied errors are handled differently using HandleAccessDeniedErrorAsync().
|
|||
// Visit https://tools.ietf.org/html/rfc6749#section-4.1.2.1 for more information.
|
|||
var errorDescription = query["error_description"]; |
|||
var errorUri = query["error_uri"]; |
|||
if (StringValues.Equals(error, "access_denied")) |
|||
{ |
|||
var result = await HandleAccessDeniedErrorAsync(properties); |
|||
if (!result.None) |
|||
{ |
|||
return result; |
|||
} |
|||
var deniedEx = new Exception("Access was denied by the resource owner or by the remote server."); |
|||
deniedEx.Data["error"] = error.ToString(); |
|||
deniedEx.Data["error_description"] = errorDescription.ToString(); |
|||
deniedEx.Data["error_uri"] = errorUri.ToString(); |
|||
|
|||
return HandleRequestResult.Fail(deniedEx, properties); |
|||
} |
|||
|
|||
var failureMessage = new StringBuilder(); |
|||
failureMessage.Append(error); |
|||
if (!StringValues.IsNullOrEmpty(errorDescription)) |
|||
{ |
|||
failureMessage.Append(";Description=").Append(errorDescription); |
|||
} |
|||
if (!StringValues.IsNullOrEmpty(errorUri)) |
|||
{ |
|||
failureMessage.Append(";Uri=").Append(errorUri); |
|||
} |
|||
|
|||
var ex = new Exception(failureMessage.ToString()); |
|||
ex.Data["error"] = error.ToString(); |
|||
ex.Data["error_description"] = errorDescription.ToString(); |
|||
ex.Data["error_uri"] = errorUri.ToString(); |
|||
|
|||
return HandleRequestResult.Fail(ex, properties); |
|||
} |
|||
|
|||
var code = query["code"]; |
|||
|
|||
if (StringValues.IsNullOrEmpty(code)) |
|||
{ |
|||
return HandleRequestResult.Fail("Code was not found.", properties); |
|||
} |
|||
|
|||
var codeExchangeContext = new OAuthCodeExchangeContext(properties, code, BuildRedirectUri(Options.CallbackPath)); |
|||
using var tokens = await ExchangeCodeAsync(codeExchangeContext); |
|||
|
|||
if (tokens.Error != null) |
|||
{ |
|||
return HandleRequestResult.Fail(tokens.Error, properties); |
|||
} |
|||
|
|||
if (string.IsNullOrEmpty(tokens.AccessToken)) |
|||
{ |
|||
return HandleRequestResult.Fail("Failed to retrieve access token.", properties); |
|||
} |
|||
|
|||
var identity = new ClaimsIdentity(ClaimsIssuer); |
|||
|
|||
if (Options.SaveTokens) |
|||
{ |
|||
var authTokens = new List<AuthenticationToken>(); |
|||
|
|||
authTokens.Add(new AuthenticationToken { Name = "access_token", Value = tokens.AccessToken }); |
|||
if (!string.IsNullOrEmpty(tokens.RefreshToken)) |
|||
{ |
|||
authTokens.Add(new AuthenticationToken { Name = "refresh_token", Value = tokens.RefreshToken }); |
|||
} |
|||
|
|||
if (!string.IsNullOrEmpty(tokens.TokenType)) |
|||
{ |
|||
authTokens.Add(new AuthenticationToken { Name = "token_type", Value = tokens.TokenType }); |
|||
} |
|||
|
|||
if (!string.IsNullOrEmpty(tokens.ExpiresIn)) |
|||
{ |
|||
int value; |
|||
if (int.TryParse(tokens.ExpiresIn, NumberStyles.Integer, CultureInfo.InvariantCulture, out value)) |
|||
{ |
|||
// https://www.w3.org/TR/xmlschema-2/#dateTime
|
|||
// https://msdn.microsoft.com/en-us/library/az4se3k1(v=vs.110).aspx
|
|||
var expiresAt = Clock.UtcNow + TimeSpan.FromSeconds(value); |
|||
authTokens.Add(new AuthenticationToken |
|||
{ |
|||
Name = "expires_at", |
|||
Value = expiresAt.ToString("o", CultureInfo.InvariantCulture) |
|||
}); |
|||
} |
|||
} |
|||
|
|||
properties.StoreTokens(authTokens); |
|||
} |
|||
|
|||
var ticket = await CreateTicketAsync(identity, properties, tokens); |
|||
if (ticket != null) |
|||
{ |
|||
return HandleRequestResult.Success(ticket); |
|||
} |
|||
else |
|||
{ |
|||
return HandleRequestResult.Fail("Failed to retrieve user information from remote server.", properties); |
|||
} |
|||
} |
|||
|
|||
protected override string FormatScope() |
|||
{ |
|||
return string.Join(",", Options.Scope); |
|||
} |
|||
|
|||
protected virtual bool IsWeChatBrowser() |
|||
{ |
|||
var userAgent = Request.Headers[HeaderNames.UserAgent].ToString(); |
|||
|
|||
return userAgent.Contains("micromessenger", StringComparison.InvariantCultureIgnoreCase); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
using LINGYUN.Abp.Authentication.WeChat; |
|||
using LINGYUN.Abp.WeChat.Security.Claims; |
|||
using Microsoft.AspNetCore.Authentication.OAuth; |
|||
using Microsoft.AspNetCore.Http; |
|||
using System.Security.Claims; |
|||
using System.Text.Json; |
|||
|
|||
namespace Microsoft.AspNetCore.Authentication.WeChat.Official |
|||
{ |
|||
public class WeChatOfficialOAuthOptions : OAuthOptions |
|||
{ |
|||
public WeChatOfficialOAuthOptions() |
|||
{ |
|||
// 用于防止初始化错误,会在OAuthHandler.InitializeHandlerAsync中进行重写
|
|||
ClientId = "WeChatOfficial"; |
|||
ClientSecret = "WeChatOfficial"; |
|||
|
|||
ClaimsIssuer = AbpAuthenticationWeChatConsts.ProviderKey; |
|||
CallbackPath = new PathString(AbpAuthenticationWeChatConsts.CallbackPath); |
|||
|
|||
AuthorizationEndpoint = AbpAuthenticationWeChatConsts.AuthorizationEndpoint; |
|||
TokenEndpoint = AbpAuthenticationWeChatConsts.TokenEndpoint; |
|||
UserInformationEndpoint = AbpAuthenticationWeChatConsts.UserInformationEndpoint; |
|||
|
|||
Scope.Add(AbpAuthenticationWeChatConsts.LoginScope); |
|||
Scope.Add(AbpAuthenticationWeChatConsts.UserInfoScope); |
|||
|
|||
// 这个原始的属性一定要写进去,框架与UserLogin.ProviderKey进行关联判断是否绑定微信
|
|||
ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "openid"); |
|||
ClaimActions.MapJsonKey(ClaimTypes.Name, "nickname"); |
|||
|
|||
// 把自定义的身份标识写进令牌
|
|||
ClaimActions.MapJsonKey(AbpWeChatClaimTypes.OpenId, "openid"); |
|||
ClaimActions.MapJsonKey(AbpWeChatClaimTypes.UnionId, "unionid");// 公众号如果与小程序关联,这个可以用上
|
|||
ClaimActions.MapJsonKey(AbpWeChatClaimTypes.NickName, "nickname"); |
|||
ClaimActions.MapJsonKey(AbpWeChatClaimTypes.Sex, "sex", ClaimValueTypes.Integer); |
|||
ClaimActions.MapJsonKey(AbpWeChatClaimTypes.Country, "country"); |
|||
ClaimActions.MapJsonKey(AbpWeChatClaimTypes.Province, "province"); |
|||
ClaimActions.MapJsonKey(AbpWeChatClaimTypes.City, "city"); |
|||
ClaimActions.MapJsonKey(AbpWeChatClaimTypes.AvatarUrl, "headimgurl"); |
|||
ClaimActions.MapCustomJson(AbpWeChatClaimTypes.Privilege, user => |
|||
{ |
|||
return string.Join(",", user.GetStrings("privilege")); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
namespace Microsoft.AspNetCore.Authentication.WeChat.Official |
|||
{ |
|||
public class WeChatOfficialStateCacheItem |
|||
{ |
|||
public string State { get; set; } |
|||
|
|||
public WeChatOfficialStateCacheItem() { } |
|||
public WeChatOfficialStateCacheItem(string state) |
|||
{ |
|||
State = state; |
|||
} |
|||
|
|||
public static string CalculateCacheKey(string correlationId, string purpose) |
|||
{ |
|||
return $"ci:{correlationId};p:{purpose ?? "null"}"; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,65 @@ |
|||
using LINGYUN.Abp.Authentication.WeChat; |
|||
using LINGYUN.Abp.WeChat; |
|||
using Microsoft.AspNetCore.Authentication.WeChat.Official; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using System; |
|||
|
|||
namespace Microsoft.AspNetCore.Authentication |
|||
{ |
|||
public static class WeChatAuthenticationExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// </summary>
|
|||
public static AuthenticationBuilder AddWeChat( |
|||
this AuthenticationBuilder builder) |
|||
{ |
|||
return builder |
|||
.AddWeChat( |
|||
AbpWeChatGlobalConsts.AuthenticationScheme, |
|||
AbpWeChatGlobalConsts.DisplayName, |
|||
options => { }); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// </summary>
|
|||
public static AuthenticationBuilder AddWeChat( |
|||
this AuthenticationBuilder builder, |
|||
Action<WeChatOfficialOAuthOptions> configureOptions) |
|||
{ |
|||
return builder |
|||
.AddWeChat( |
|||
AbpWeChatGlobalConsts.AuthenticationScheme, |
|||
AbpWeChatGlobalConsts.DisplayName, |
|||
configureOptions); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// </summary>
|
|||
public static AuthenticationBuilder AddWeChat( |
|||
this AuthenticationBuilder builder, |
|||
string authenticationScheme, |
|||
Action<WeChatOfficialOAuthOptions> configureOptions) |
|||
{ |
|||
return builder |
|||
.AddWeChat( |
|||
authenticationScheme, |
|||
AbpAuthenticationWeChatConsts.DisplayName, |
|||
configureOptions); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// </summary>
|
|||
public static AuthenticationBuilder AddWeChat( |
|||
this AuthenticationBuilder builder, |
|||
string authenticationScheme, |
|||
string displayName, |
|||
Action<WeChatOfficialOAuthOptions> configureOptions) |
|||
{ |
|||
return builder |
|||
.AddOAuth<WeChatOfficialOAuthOptions, WeChatOfficialOAuthHandler>( |
|||
authenticationScheme, |
|||
displayName, |
|||
configureOptions); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using Microsoft.AspNetCore.Http; |
|||
|
|||
namespace Microsoft.AspNetCore.Builder |
|||
{ |
|||
public static class IdentityServerApplicationBuilderExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// 启用中间件可以处理微信服务器消息
|
|||
/// 用于验证消息是否来自于微信服务器
|
|||
/// </summary>
|
|||
/// <param name="builder"></param>
|
|||
/// <remarks>
|
|||
/// 也可以用Controller的形式来实现
|
|||
/// </remarks>
|
|||
/// <returns></returns>
|
|||
public static IApplicationBuilder UseWeChatSignature(this IApplicationBuilder builder) |
|||
{ |
|||
builder.UseMiddleware<WeChatOfficialSignatureMiddleware>(); |
|||
return builder; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,76 @@ |
|||
using LINGYUN.Abp.WeChat.Official; |
|||
using System; |
|||
using System.Collections; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.DependencyInjection; |
|||
|
|||
namespace Microsoft.AspNetCore.Http |
|||
{ |
|||
public class WeChatOfficialSignatureMiddleware : IMiddleware, ITransientDependency |
|||
{ |
|||
protected AbpWeChatOfficialOptionsFactory WeChatOfficialOptionsFactory { get; } |
|||
public WeChatOfficialSignatureMiddleware( |
|||
AbpWeChatOfficialOptionsFactory weChatOfficialOptionsFactory) |
|||
{ |
|||
WeChatOfficialOptionsFactory = weChatOfficialOptionsFactory; |
|||
} |
|||
|
|||
public async Task InvokeAsync(HttpContext context, RequestDelegate next) |
|||
{ |
|||
if (context.Request.Path.HasValue) |
|||
{ |
|||
var options = await WeChatOfficialOptionsFactory.CreateAsync(); |
|||
|
|||
var requestPath = context.Request.Path.Value; |
|||
// 访问地址是否与定义的地址匹配
|
|||
if (requestPath.Equals(options.Url)) |
|||
{ |
|||
var timestamp = context.Request.Query["timestamp"]; |
|||
var nonce = context.Request.Query["nonce"]; |
|||
var signature = context.Request.Query["signature"]; |
|||
var echostr = context.Request.Query["echostr"]; |
|||
// 验证消息合法性
|
|||
var check = CheckWeChatSignature(options.Token, timestamp, nonce, signature); |
|||
if (check) |
|||
{ |
|||
// 验证通过需要把微信服务器传递的字符原封不动传回
|
|||
await context.Response.WriteAsync(echostr); |
|||
return; |
|||
} |
|||
// 微信消息验证不通过
|
|||
throw new AbpException("Invalid wechat signature"); |
|||
} |
|||
} |
|||
// 不属于微信的消息进入下一个中间件
|
|||
await next(context); |
|||
} |
|||
|
|||
protected bool CheckWeChatSignature(string token, string timestamp, string nonce, string signature) |
|||
{ |
|||
var al = new ArrayList |
|||
{ |
|||
token, |
|||
timestamp, |
|||
nonce |
|||
}; |
|||
// step1 排序
|
|||
al.Sort(); |
|||
string signatureStr = string.Empty; |
|||
// step2 拼接
|
|||
for (int i = 0; i < al.Count; i++) |
|||
{ |
|||
signatureStr += al[i]; |
|||
} |
|||
// step3 SHA1加密
|
|||
byte[] bytes_out = signatureStr.Sha1(); |
|||
string result = BitConverter.ToString(bytes_out).Replace("-", ""); |
|||
// step4 比对
|
|||
if (result.Equals(signature, StringComparison.CurrentCultureIgnoreCase)) |
|||
{ |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using System.Security.Cryptography; |
|||
|
|||
namespace System |
|||
{ |
|||
internal static class BytesExtensions |
|||
{ |
|||
public static byte[] Sha1(this byte[] data) |
|||
{ |
|||
using (var sha = SHA1.Create()) |
|||
{ |
|||
var hashBytes = sha.ComputeHash(data); |
|||
return hashBytes; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System.Security.Cryptography; |
|||
using System.Text; |
|||
|
|||
namespace System |
|||
{ |
|||
internal static class StringExtensions |
|||
{ |
|||
public static byte[] Sha1(this string str) |
|||
{ |
|||
using (var sha = SHA1.Create()) |
|||
{ |
|||
var hashBytes = sha.ComputeHash(Encoding.ASCII.GetBytes(str)); |
|||
return hashBytes; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,63 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace System.Text.Json |
|||
{ |
|||
internal static class JsonElementExtensions |
|||
{ |
|||
public static IEnumerable<string> GetRootStrings(this JsonDocument json, string key) |
|||
{ |
|||
return json.RootElement.GetStrings(key); |
|||
} |
|||
|
|||
public static IEnumerable<string> GetStrings(this JsonElement json, string key) |
|||
{ |
|||
var result = new List<string>(); |
|||
|
|||
if (json.TryGetProperty(key, out JsonElement property) && property.ValueKind == JsonValueKind.Array) |
|||
{ |
|||
foreach (var jsonProp in property.EnumerateArray()) |
|||
{ |
|||
result.Add(jsonProp.GetString()); |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
public static string GetRootString(this JsonDocument json, string key, string defaultValue = "") |
|||
{ |
|||
if (json.RootElement.TryGetProperty(key, out JsonElement property)) |
|||
{ |
|||
return property.GetString(); |
|||
} |
|||
return defaultValue; |
|||
} |
|||
|
|||
public static string GetString(this JsonElement json, string key, string defaultValue = "") |
|||
{ |
|||
if (json.TryGetProperty(key, out JsonElement property)) |
|||
{ |
|||
return property.GetString(); |
|||
} |
|||
return defaultValue; |
|||
} |
|||
|
|||
public static int GetRootInt32(this JsonDocument json, string key, int defaultValue = 0) |
|||
{ |
|||
if (json.RootElement.TryGetProperty(key, out JsonElement property) && property.TryGetInt32(out int value)) |
|||
{ |
|||
return value; |
|||
} |
|||
return defaultValue; |
|||
} |
|||
|
|||
public static int GetInt32(this JsonElement json, string key, int defaultValue = 0) |
|||
{ |
|||
if (json.TryGetProperty(key, out JsonElement property) && property.TryGetInt32(out int value)) |
|||
{ |
|||
return value; |
|||
} |
|||
return defaultValue; |
|||
} |
|||
} |
|||
} |
|||
@ -1,10 +1,11 @@ |
|||
using System.Threading.Tasks; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.BackgroundWorkers; |
|||
|
|||
namespace LINGYUN.Abp.BackgroundWorkers.Hangfire |
|||
{ |
|||
public interface IHangfireBackgroundWorkerAdapter : IBackgroundWorker |
|||
{ |
|||
Task ExecuteAsync(); |
|||
Task ExecuteAsync(CancellationToken cancellationToken = default); |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,40 @@ |
|||
# LINGYUN.Abp.Wrapper |
|||
|
|||
包装器模块 |
|||
|
|||
## 配置使用 |
|||
|
|||
```csharp |
|||
[DependsOn(typeof(AbpWrapperModule))] |
|||
public class YouProjectModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpWrapperOptions>(options => |
|||
{ |
|||
// 启用包装器 |
|||
options.IsEnabled = true; |
|||
}); |
|||
} |
|||
} |
|||
``` |
|||
## 配置项说明 |
|||
|
|||
* AbpWrapperOptions.IsEnabled 是否包装返回结果,默认: false |
|||
* AbpWrapperOptions.CodeWithUnhandled 出现未处理异常时的返回错误代码,默认500 |
|||
* AbpWrapperOptions.CodeWithSuccess 处理成功返回代码,默认0 |
|||
* AbpWrapperOptions.ErrorWithEmptyResult 请求资源时,如果资源为空是否返回错误消息,默认false |
|||
* AbpWrapperOptions.HttpStatusCode 包装后的Http响应代码, 默认: 200 |
|||
* AbpWrapperOptions.CodeWithEmptyResult 当返回空对象时返回错误代码,默认: 404 |
|||
* AbpWrapperOptions.MessageWithEmptyResult 当返回空对象时返回错误消息, 默认: Not Found |
|||
|
|||
* AbpWrapperOptions.IgnorePrefixUrls 指定哪些Url开头的地址不需要处理 |
|||
* AbpWrapperOptions.IgnoreNamespaces 指定哪些命名空间开头不需要处理 |
|||
* AbpWrapperOptions.IgnoreControllers 指定哪些控制器不需要处理 |
|||
* AbpWrapperOptions.IgnoreReturnTypes 指定哪些返回结果类型不需要处理 |
|||
* AbpWrapperOptions.IgnoreExceptions 指定哪些异常类型不需要处理 |
|||
* AbpWrapperOptions.IgnoredInterfaces 指定哪些接口不需要处理(默认实现**IWrapDisabled**接口不进行处理) |
|||
|
|||
|
|||
## 其他 |
|||
|
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,25 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.props" /> |
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<None Remove="LINGYUN\Abp\OpenIddict\Localization\Resources\*.json" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<EmbeddedResource Include="LINGYUN\Abp\OpenIddict\Localization\Resources\*.json" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.Authorization.Abstractions" Version="$(VoloAbpPackageVersion)" /> |
|||
<PackageReference Include="Volo.Abp.Ddd.Application.Contracts" Version="$(VoloAbpPackageVersion)" /> |
|||
<PackageReference Include="Volo.Abp.OpenIddict.Domain.Shared" Version="$(VoloAbpPackageVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,29 @@ |
|||
using Volo.Abp.Authorization; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.OpenIddict; |
|||
using Volo.Abp.OpenIddict.Localization; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpAuthorizationAbstractionsModule), |
|||
typeof(AbpOpenIddictDomainSharedModule))] |
|||
public class AbpOpenIddictApplicationContractsModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<AbpOpenIddictApplicationContractsModule>(); |
|||
}); |
|||
|
|||
Configure<AbpLocalizationOptions>(options => |
|||
{ |
|||
options.Resources |
|||
.Get<AbpOpenIddictResource>() |
|||
.AddVirtualJson("/LINGYUN/Abp/OpenIddict/Localization/Resources"); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Applications; |
|||
|
|||
public interface IOpenIddictApplicationAppService : |
|||
ICrudAppService< |
|||
OpenIddictApplicationDto, |
|||
Guid, |
|||
OpenIddictApplicationGetListInput, |
|||
OpenIddictApplicationCreateDto, |
|||
OpenIddictApplicationUpdateDto> |
|||
{ |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Applications; |
|||
|
|||
[Serializable] |
|||
public class OpenIddictApplicationCreateDto : OpenIddictApplicationCreateOrUpdateDto |
|||
{ |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Volo.Abp.ObjectExtending; |
|||
using Volo.Abp.OpenIddict.Applications; |
|||
using Volo.Abp.Validation; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Applications; |
|||
|
|||
public abstract class OpenIddictApplicationCreateOrUpdateDto : ExtensibleObject |
|||
{ |
|||
[Required] |
|||
[DynamicStringLength(typeof(OpenIddictApplicationConsts), nameof(OpenIddictApplicationConsts.ClientIdMaxLength))] |
|||
public string ClientId { get; set; } |
|||
|
|||
public string ClientSecret { get; set; } |
|||
|
|||
[DynamicStringLength(typeof(OpenIddictApplicationConsts), nameof(OpenIddictApplicationConsts.ConsentTypeMaxLength))] |
|||
public string ConsentType { get; set; } |
|||
|
|||
public string DisplayName { get; set; } |
|||
|
|||
public Dictionary<string, string> DisplayNames { get; set; } = new Dictionary<string, string>(); |
|||
|
|||
public List<string> Endpoints { get; set; } = new List<string>(); |
|||
public List<string> GrantTypes { get; set; } = new List<string>(); |
|||
public List<string> ResponseTypes { get; set; } = new List<string>(); |
|||
public List<string> Scopes { get; set; } = new List<string>(); |
|||
|
|||
public List<string> PostLogoutRedirectUris { get; set; } = new List<string>(); |
|||
|
|||
public Dictionary<string, string> Properties { get; set; } = new Dictionary<string, string>(); |
|||
|
|||
public List<string> RedirectUris { get; set; } = new List<string>(); |
|||
|
|||
public List<string> Requirements { get; set; } = new List<string>(); |
|||
|
|||
[DynamicStringLength(typeof(OpenIddictApplicationConsts), nameof(OpenIddictApplicationConsts.TypeMaxLength))] |
|||
public string Type { get; set; } |
|||
|
|||
public string ClientUri { get; set; } |
|||
|
|||
public string LogoUri { get; set; } |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Applications; |
|||
|
|||
[Serializable] |
|||
public class OpenIddictApplicationDto : ExtensibleAuditedEntityDto<Guid> |
|||
{ |
|||
public string ClientId { get; set; } |
|||
public string ClientSecret { get; set; } |
|||
public string ConsentType { get; set; } |
|||
public string DisplayName { get; set; } |
|||
public Dictionary<string, string> DisplayNames { get; set; } = new Dictionary<string, string>(); |
|||
public List<string> Endpoints { get; set; } = new List<string>(); |
|||
public List<string> GrantTypes { get; set; } = new List<string>(); |
|||
public List<string> ResponseTypes { get; set; } = new List<string>(); |
|||
public List<string> Scopes { get; set; } = new List<string>(); |
|||
public List<string> PostLogoutRedirectUris { get; set; } = new List<string>(); |
|||
public Dictionary<string, string> Properties { get; set; } = new Dictionary<string, string>(); |
|||
public List<string> RedirectUris { get; set; } = new List<string>(); |
|||
public List<string> Requirements { get; set; } = new List<string>(); |
|||
public string Type { get; set; } |
|||
public string ClientUri { get; set; } |
|||
public string LogoUri { get; set; } |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Applications; |
|||
|
|||
[Serializable] |
|||
public class OpenIddictApplicationGetListInput : PagedAndSortedResultRequestDto |
|||
{ |
|||
public string Filter { get; set; } |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Applications; |
|||
|
|||
[Serializable] |
|||
public class OpenIddictApplicationUpdateDto : OpenIddictApplicationCreateOrUpdateDto |
|||
{ |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Authorizations; |
|||
|
|||
public interface IOpenIddictAuthorizationAppService : |
|||
IReadOnlyAppService< |
|||
OpenIddictAuthorizationDto, |
|||
Guid, |
|||
OpenIddictAuthorizationGetListInput>, |
|||
IDeleteAppService<Guid> |
|||
{ |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Authorizations; |
|||
|
|||
[Serializable] |
|||
public class OpenIddictAuthorizationDto : ExtensibleAuditedEntityDto<Guid> |
|||
{ |
|||
public Guid? ApplicationId { get; set; } |
|||
public DateTime? CreationDate { get; set; } |
|||
public Dictionary<string, string> Properties { get; set; } = new Dictionary<string, string>(); |
|||
public List<string> Scopes { get; set; } = new List<string>(); |
|||
public string Status { get; set; } |
|||
public string Subject { get; set; } |
|||
public string Type { get; set; } |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Authorizations; |
|||
|
|||
[Serializable] |
|||
public class OpenIddictAuthorizationGetListInput : PagedAndSortedResultRequestDto |
|||
{ |
|||
public string Filter { get; set; } |
|||
public string Subject { get; set; } |
|||
public Guid? ClientId { get; set; } |
|||
public string Status { get; set; } |
|||
public string Type { get; set; } |
|||
public DateTime? BeginCreationTime { get; set; } |
|||
public DateTime? EndCreationTime { get; set; } |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
{ |
|||
"culture": "en", |
|||
"texts": { |
|||
"Permissions:OpenIddict": "OpenIddict", |
|||
"Permissions:Applications": "Applications", |
|||
"Permissions:Scopes": "Scopes", |
|||
"Permissions:Tokens": "Tokens", |
|||
"Permissions:Create": "Create", |
|||
"Permissions:Update": "Update", |
|||
"Permissions:Delete": "Delete", |
|||
"Permissions:ManagePermissions": "Manage Permissions", |
|||
"OpenIddict:Applications:0001": "The application client id {ClientId} already exists!", |
|||
"OpenIddict:Scopes:0001": "The scope name {Name} already exists!", |
|||
"DisplayName:ClientId": "Client Id", |
|||
"DisplayName:ClientSecret": "Client Secret", |
|||
"DisplayName:ConsentType": "Consent Type", |
|||
"DisplayName:DisplayName": "Display Name", |
|||
"DisplayName:DisplayNames": "Display Names", |
|||
"DisplayName:Permissions": "Permissions", |
|||
"DisplayName:PostLogoutRedirectUris": "Post Logout Redirect Uris", |
|||
"DisplayName:Properties": "Properties", |
|||
"DisplayName:RedirectUris": "Redirect Uris", |
|||
"DisplayName:Requirements": "Requirements", |
|||
"DisplayName:Type": "Type", |
|||
"DisplayName:ClientUri": "Client Uri", |
|||
"DisplayName:LogoUri": "Logo", |
|||
"DisplayName:ApplicationId": "Application Id", |
|||
"DisplayName:CreationDate": "Creation Date", |
|||
"DisplayName:Scopes": "Scopes", |
|||
"DisplayName:Status": "Status", |
|||
"DisplayName:Subject": "Subject", |
|||
"DisplayName:Description": "Description", |
|||
"DisplayName:Descriptions": "Descriptions", |
|||
"DisplayName:Name": "Name", |
|||
"DisplayName:Resources": "Resources", |
|||
"DisplayName:AuthorizationId": "Authorization Id", |
|||
"DisplayName:ExpirationDate": "Expiration Date", |
|||
"DisplayName:Payload": "Payload", |
|||
"DisplayName:RedemptionDate": "Redemption Date", |
|||
"DisplayName:ReferenceId": "Reference Id", |
|||
"DisplayName:LastModificationTime": "Modify time", |
|||
"DisplayName:CreationTime": "Creation time" |
|||
} |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
{ |
|||
"culture": "zh-Hans", |
|||
"texts": { |
|||
"Permissions:OpenIddict": "OpenIddict", |
|||
"Permissions:Applications": "应用", |
|||
"Permissions:Scopes": "范围", |
|||
"Permissions:Tokens": "令牌", |
|||
"Permissions:Create": "创建", |
|||
"Permissions:Update": "编辑", |
|||
"Permissions:Delete": "删除", |
|||
"Permissions:ManagePermissions": "管理权限", |
|||
"OpenIddict:Applications:0001": "应用标识 {ClientId} 已经存在!", |
|||
"OpenIddict:Scopes:0001": "范围名称 {Name} 已经存在!", |
|||
"DisplayName:ClientId": "客户端标识", |
|||
"DisplayName:ClientSecret": "客户端密钥", |
|||
"DisplayName:ConsentType": "同意类型", |
|||
"DisplayName:DisplayName": "显示名称", |
|||
"DisplayName:DisplayNames": "显示名称", |
|||
"DisplayName:Permissions": "权限", |
|||
"DisplayName:PostLogoutRedirectUris": "注销后重定向Uri", |
|||
"DisplayName:Properties": "属性", |
|||
"DisplayName:RedirectUris": "重定向Uri", |
|||
"DisplayName:Requirements": "需求", |
|||
"DisplayName:Type": "类型", |
|||
"DisplayName:ClientUri": "客户端Uri", |
|||
"DisplayName:LogoUri": "客户端Logo", |
|||
"DisplayName:ApplicationId": "应用标识", |
|||
"DisplayName:CreationDate": "创建日期", |
|||
"DisplayName:Scopes": "范围", |
|||
"DisplayName:Status": "状态", |
|||
"DisplayName:Subject": "主体", |
|||
"DisplayName:Description": "描述", |
|||
"DisplayName:Descriptions": "描述", |
|||
"DisplayName:Name": "名称", |
|||
"DisplayName:Resources": "资源", |
|||
"DisplayName:AuthorizationId": "授权标识", |
|||
"DisplayName:ExpirationDate": "过期时间", |
|||
"DisplayName:Payload": "有效载荷", |
|||
"DisplayName:RedemptionDate": "偿还时间", |
|||
"DisplayName:ReferenceId": "引用标识", |
|||
"DisplayName:LastModificationTime": "修改时间", |
|||
"DisplayName:CreationTime": "创建时间" |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
namespace LINGYUN.Abp.OpenIddict; |
|||
|
|||
public static class OpenIddictApplicationErrorCodes |
|||
{ |
|||
private const string Namespace = "OpenIddict:"; |
|||
|
|||
public static class Applications |
|||
{ |
|||
public const string ClientIdExisted = Namespace + "Applications:0001"; |
|||
} |
|||
|
|||
public static class Authorizations |
|||
{ |
|||
|
|||
} |
|||
|
|||
public static class Scopes |
|||
{ |
|||
public const string NameExisted = Namespace + "Scopes:0001"; |
|||
} |
|||
|
|||
public static class Tokens |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
namespace LINGYUN.Abp.OpenIddict; |
|||
|
|||
public class OpenIddictRemoteServiceConsts |
|||
{ |
|||
public const string RemoteServiceName = "AbpOpenIddict"; |
|||
|
|||
public const string ModuleName = "openiddict"; |
|||
} |
|||
@ -0,0 +1,84 @@ |
|||
using Volo.Abp.Authorization.Permissions; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.MultiTenancy; |
|||
using Volo.Abp.OpenIddict.Localization; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Permissions |
|||
{ |
|||
public class AbpIdentityServerPermissionDefinitionProvider : PermissionDefinitionProvider |
|||
{ |
|||
public override void Define(IPermissionDefinitionContext context) |
|||
{ |
|||
var openIddictGroup = context.GetGroupOrNull(AbpOpenIddictPermissions.GroupName); |
|||
if (openIddictGroup == null) |
|||
{ |
|||
openIddictGroup = context |
|||
.AddGroup( |
|||
name: AbpOpenIddictPermissions.GroupName, |
|||
displayName: L("Permissions:OpenIddict"), |
|||
multiTenancySide: MultiTenancySides.Host); |
|||
} |
|||
|
|||
var applications = openIddictGroup.AddPermission( |
|||
AbpOpenIddictPermissions.Applications.Default, |
|||
L("Permissions:Applications"), |
|||
MultiTenancySides.Host); |
|||
applications.AddChild( |
|||
AbpOpenIddictPermissions.Applications.Create, |
|||
L("Permissions:Create"), |
|||
MultiTenancySides.Host); |
|||
applications.AddChild( |
|||
AbpOpenIddictPermissions.Applications.Update, |
|||
L("Permissions:Update"), |
|||
MultiTenancySides.Host); |
|||
applications.AddChild( |
|||
AbpOpenIddictPermissions.Applications.Delete, |
|||
L("Permissions:Delete"), |
|||
MultiTenancySides.Host); |
|||
applications.AddChild( |
|||
AbpOpenIddictPermissions.Applications.ManagePermissions, |
|||
L("Permissions:ManagePermissions"), |
|||
MultiTenancySides.Host); |
|||
|
|||
var authorizations = openIddictGroup.AddPermission( |
|||
AbpOpenIddictPermissions.Authorizations.Default, |
|||
L("Permissions:Authorizations"), |
|||
MultiTenancySides.Host); |
|||
authorizations.AddChild( |
|||
AbpOpenIddictPermissions.Authorizations.Delete, |
|||
L("Permissions:Delete"), |
|||
MultiTenancySides.Host); |
|||
|
|||
var scopes = openIddictGroup.AddPermission( |
|||
AbpOpenIddictPermissions.Scopes.Default, |
|||
L("Permissions:Scopes"), |
|||
MultiTenancySides.Host); |
|||
scopes.AddChild( |
|||
AbpOpenIddictPermissions.Scopes.Create, |
|||
L("Permissions:Create"), |
|||
MultiTenancySides.Host); |
|||
scopes.AddChild( |
|||
AbpOpenIddictPermissions.Scopes.Update, |
|||
L("Permissions:Update"), |
|||
MultiTenancySides.Host); |
|||
scopes.AddChild( |
|||
AbpOpenIddictPermissions.Scopes.Delete, |
|||
L("Permissions:Delete"), |
|||
MultiTenancySides.Host); |
|||
|
|||
var tokens = openIddictGroup.AddPermission( |
|||
AbpOpenIddictPermissions.Tokens.Default, |
|||
L("Permissions:Tokens"), |
|||
MultiTenancySides.Host); |
|||
tokens.AddChild( |
|||
AbpOpenIddictPermissions.Tokens.Delete, |
|||
L("Permissions:Delete"), |
|||
MultiTenancySides.Host); |
|||
} |
|||
|
|||
protected virtual LocalizableString L(string name) |
|||
{ |
|||
return LocalizableString.Create<AbpOpenIddictResource>(name); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
namespace LINGYUN.Abp.OpenIddict.Permissions |
|||
{ |
|||
public class AbpOpenIddictPermissions |
|||
{ |
|||
public const string GroupName = "AbpOpenIddict"; |
|||
|
|||
public static class Applications |
|||
{ |
|||
public const string Default = GroupName + ".Applications"; |
|||
public const string Create = Default + ".Create"; |
|||
public const string Update = Default + ".Update"; |
|||
public const string Delete = Default + ".Delete"; |
|||
public const string ManagePermissions = Default + ".ManagePermissions"; |
|||
} |
|||
|
|||
public static class Authorizations |
|||
{ |
|||
public const string Default = GroupName + ".Authorizations"; |
|||
public const string Delete = Default + ".Delete"; |
|||
} |
|||
|
|||
public static class Scopes |
|||
{ |
|||
public const string Default = GroupName + ".Scopes"; |
|||
public const string Create = Default + ".Create"; |
|||
public const string Update = Default + ".Update"; |
|||
public const string Delete = Default + ".Delete"; |
|||
} |
|||
|
|||
public static class Tokens |
|||
{ |
|||
public const string Default = GroupName + ".Tokens"; |
|||
public const string Delete = Default + ".Delete"; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,14 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Scopes; |
|||
|
|||
public interface IOpenIddictScopeAppService : |
|||
ICrudAppService< |
|||
OpenIddictScopeDto, |
|||
Guid, |
|||
OpenIddictScopeGetListInput, |
|||
OpenIddictScopeCreateDto, |
|||
OpenIddictScopeUpdateDto> |
|||
{ |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Scopes; |
|||
|
|||
[Serializable] |
|||
public class OpenIddictScopeCreateDto : OpenIddictScopeCreateOrUpdateDto |
|||
{ |
|||
} |
|||
@ -0,0 +1,27 @@ |
|||
using System.Collections.Generic; |
|||
using System.ComponentModel.DataAnnotations; |
|||
using Volo.Abp.ObjectExtending; |
|||
using Volo.Abp.OpenIddict.Scopes; |
|||
using Volo.Abp.Validation; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Scopes; |
|||
|
|||
public abstract class OpenIddictScopeCreateOrUpdateDto : ExtensibleObject |
|||
{ |
|||
|
|||
public string Description { get; set; } |
|||
|
|||
public Dictionary<string, string> Descriptions { get; set; } = new Dictionary<string, string>(); |
|||
|
|||
public string DisplayName { get; set; } |
|||
|
|||
public Dictionary<string, string> DisplayNames { get; set; } = new Dictionary<string, string>(); |
|||
|
|||
[Required] |
|||
[DynamicStringLength(typeof(OpenIddictScopeConsts), nameof(OpenIddictScopeConsts.NameMaxLength))] |
|||
public string Name { get; set; } |
|||
|
|||
public Dictionary<string, string> Properties { get; set; } = new Dictionary<string, string>(); |
|||
|
|||
public List<string> Resources { get; set; } |
|||
} |
|||
@ -0,0 +1,24 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Scopes; |
|||
|
|||
[Serializable] |
|||
public class OpenIddictScopeDto : ExtensibleAuditedEntityDto<Guid> |
|||
{ |
|||
public string Description { get; set; } |
|||
|
|||
public Dictionary<string, string> Descriptions { get; set; } = new Dictionary<string, string>(); |
|||
|
|||
|
|||
public string DisplayName { get; set; } |
|||
|
|||
public Dictionary<string, string> DisplayNames { get; set; } = new Dictionary<string, string>(); |
|||
|
|||
public string Name { get; set; } |
|||
|
|||
public Dictionary<string, string> Properties { get; set; } = new Dictionary<string, string>(); |
|||
|
|||
public List<string> Resources { get; set; } |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Scopes; |
|||
|
|||
[Serializable] |
|||
public class OpenIddictScopeGetListInput : PagedAndSortedResultRequestDto |
|||
{ |
|||
public string Filter { get; set; } |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
using System; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Scopes; |
|||
|
|||
[Serializable] |
|||
public class OpenIddictScopeUpdateDto : OpenIddictScopeCreateOrUpdateDto |
|||
{ |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Tokens; |
|||
|
|||
public interface IOpenIddictTokenAppService : |
|||
IReadOnlyAppService< |
|||
OpenIddictTokenDto, |
|||
Guid, |
|||
OpenIddictTokenGetListInput>, |
|||
IDeleteAppService<Guid> |
|||
{ |
|||
} |
|||
@ -0,0 +1,30 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Tokens; |
|||
|
|||
[Serializable] |
|||
public class OpenIddictTokenDto : ExtensibleAuditedEntityDto<Guid> |
|||
{ |
|||
public Guid? ApplicationId { get; set; } |
|||
|
|||
public Guid? AuthorizationId { get; set; } |
|||
|
|||
public DateTime? CreationDate { get; set; } |
|||
|
|||
public DateTime? ExpirationDate { get; set; } |
|||
|
|||
public string Payload { get; set; } |
|||
|
|||
public string Properties { get; set; } |
|||
|
|||
public DateTime? RedemptionDate { get; set; } |
|||
|
|||
public string ReferenceId { get; set; } |
|||
|
|||
public string Status { get; set; } |
|||
|
|||
public string Subject { get; set; } |
|||
|
|||
public string Type { get; set; } |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Tokens; |
|||
|
|||
[Serializable] |
|||
public class OpenIddictTokenGetListInput : PagedAndSortedResultRequestDto |
|||
{ |
|||
public string Filter { get; set; } |
|||
public Guid? ClientId { get; set; } |
|||
public Guid? AuthorizationId { get; set; } |
|||
public string Subject { get; set; } |
|||
public string Status { get; set; } |
|||
public string Type { get; set; } |
|||
public string ReferenceId { get; set; } |
|||
public DateTime? BeginExpirationDate { get; set; } |
|||
public DateTime? EndExpirationDate { get; set; } |
|||
public DateTime? BeginCreationTime { get; set; } |
|||
public DateTime? EndCreationTime { get; set; } |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,20 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.props" /> |
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.1</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.Ddd.Application" Version="$(VoloAbpPackageVersion)" /> |
|||
<PackageReference Include="Volo.Abp.OpenIddict.Domain" Version="$(VoloAbpPackageVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.OpenIddict.Application.Contracts\LINGYUN.Abp.OpenIddict.Application.Contracts.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,13 @@ |
|||
using Volo.Abp.Application; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.OpenIddict; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpOpenIddictApplicationContractsModule), |
|||
typeof(AbpOpenIddictDomainModule), |
|||
typeof(AbpDddApplicationModule))] |
|||
public class AbpOpenIddictApplicationModule : AbpModule |
|||
{ |
|||
} |
|||
@ -0,0 +1,86 @@ |
|||
using LINGYUN.Abp.OpenIddict.Permissions; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.OpenIddict.Applications; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Applications; |
|||
|
|||
[Authorize(AbpOpenIddictPermissions.Applications.Default)] |
|||
public class OpenIddictApplicationAppService : OpenIddictApplicationServiceBase, IOpenIddictApplicationAppService |
|||
{ |
|||
protected IOpenIddictApplicationRepository Repository { get; } |
|||
|
|||
public OpenIddictApplicationAppService( |
|||
IOpenIddictApplicationRepository repository) |
|||
{ |
|||
Repository = repository; |
|||
} |
|||
|
|||
public async virtual Task<OpenIddictApplicationDto> GetAsync(Guid id) |
|||
{ |
|||
var application = await Repository.GetAsync(id); |
|||
|
|||
return application.ToDto(JsonSerializer); |
|||
} |
|||
|
|||
public async virtual Task<PagedResultDto<OpenIddictApplicationDto>> GetListAsync(OpenIddictApplicationGetListInput input) |
|||
{ |
|||
var totalCount = await Repository.GetCountAsync(input.Filter); |
|||
var entites = await Repository.GetListAsync(input.Sorting, input.SkipCount, input.MaxResultCount, input.Filter); |
|||
|
|||
return new PagedResultDto<OpenIddictApplicationDto>(totalCount, |
|||
entites.Select(entity => entity.ToDto(JsonSerializer)).ToList()); |
|||
} |
|||
|
|||
[Authorize(AbpOpenIddictPermissions.Applications.Create)] |
|||
public async virtual Task<OpenIddictApplicationDto> CreateAsync(OpenIddictApplicationCreateDto input) |
|||
{ |
|||
if (await Repository.FindByClientIdAsync(input.ClientId) != null) |
|||
{ |
|||
throw new BusinessException(OpenIddictApplicationErrorCodes.Applications.ClientIdExisted) |
|||
.WithData(nameof(OpenIddictApplication.ClientId), input.ClientId); |
|||
} |
|||
|
|||
var application = new OpenIddictApplication(GuidGenerator.Create()); |
|||
application = input.ToEntity(application, JsonSerializer); |
|||
|
|||
application = await Repository.InsertAsync(application); |
|||
|
|||
await CurrentUnitOfWork.SaveChangesAsync(); |
|||
|
|||
return application.ToDto(JsonSerializer); |
|||
} |
|||
|
|||
[Authorize(AbpOpenIddictPermissions.Applications.Update)] |
|||
public async virtual Task<OpenIddictApplicationDto> UpdateAsync(Guid id, OpenIddictApplicationUpdateDto input) |
|||
{ |
|||
var application = await Repository.GetAsync(id); |
|||
|
|||
if (!string.Equals(application.ClientId, input.ClientId) && |
|||
await Repository.FindByClientIdAsync(input.ClientId) != null) |
|||
{ |
|||
throw new BusinessException(OpenIddictApplicationErrorCodes.Applications.ClientIdExisted) |
|||
.WithData(nameof(OpenIddictApplicationCreateDto.ClientId), input.ClientId); |
|||
} |
|||
|
|||
application = input.ToEntity(application, JsonSerializer); |
|||
|
|||
application = await Repository.UpdateAsync(application); |
|||
|
|||
await CurrentUnitOfWork.SaveChangesAsync(); |
|||
|
|||
return application.ToDto(JsonSerializer); |
|||
} |
|||
|
|||
[Authorize(AbpOpenIddictPermissions.Applications.Delete)] |
|||
public async virtual Task DeleteAsync(Guid id) |
|||
{ |
|||
await Repository.DeleteAsync(id); |
|||
|
|||
await CurrentUnitOfWork.SaveChangesAsync(); |
|||
} |
|||
} |
|||
@ -0,0 +1,162 @@ |
|||
using OpenIddict.Abstractions; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Json; |
|||
using Volo.Abp.OpenIddict.Applications; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Applications; |
|||
|
|||
internal static class OpenIddictApplicationExtensions |
|||
{ |
|||
public static OpenIddictApplication ToEntity(this OpenIddictApplicationCreateOrUpdateDto dto, OpenIddictApplication entity, IJsonSerializer jsonSerializer) |
|||
{ |
|||
Check.NotNull(dto, nameof(dto)); |
|||
Check.NotNull(entity, nameof(entity)); |
|||
|
|||
entity.ClientId = dto.ClientId; |
|||
entity.ClientSecret = dto.ClientSecret; |
|||
entity.ConsentType = dto.ConsentType; |
|||
entity.DisplayName = dto.DisplayName; |
|||
entity.DisplayNames = jsonSerializer.Serialize(dto.DisplayNames); |
|||
entity.PostLogoutRedirectUris = jsonSerializer.Serialize(dto.PostLogoutRedirectUris); |
|||
entity.Properties = jsonSerializer.Serialize(dto.Properties); |
|||
entity.RedirectUris = jsonSerializer.Serialize(dto.RedirectUris); |
|||
entity.Type = dto.Type; |
|||
entity.ClientUri = dto.ClientUri; |
|||
entity.LogoUri = dto.LogoUri; |
|||
|
|||
var requirements = new List<string>(); |
|||
requirements.AddRange( |
|||
dto.Requirements.Select(requirement => |
|||
{ |
|||
if (!requirement.StartsWith(OpenIddictConstants.Requirements.Prefixes.Feature)) |
|||
{ |
|||
return OpenIddictConstants.Requirements.Prefixes.Feature + requirement; |
|||
} |
|||
return requirement; |
|||
})); |
|||
entity.Requirements = jsonSerializer.Serialize(requirements); |
|||
|
|||
var permissions = new List<string>(); |
|||
permissions.AddRange( |
|||
dto.Endpoints.Select(endpoint => |
|||
{ |
|||
if (!endpoint.StartsWith(OpenIddictConstants.Permissions.Prefixes.Endpoint)) |
|||
{ |
|||
return OpenIddictConstants.Permissions.Prefixes.Endpoint + endpoint; |
|||
} |
|||
return endpoint; |
|||
})); |
|||
permissions.AddRange( |
|||
dto.GrantTypes.Select(grantType => |
|||
{ |
|||
if (!grantType.StartsWith(OpenIddictConstants.Permissions.Prefixes.GrantType)) |
|||
{ |
|||
return OpenIddictConstants.Permissions.Prefixes.GrantType + grantType; |
|||
} |
|||
return grantType; |
|||
})); |
|||
permissions.AddRange( |
|||
dto.ResponseTypes.Select(responseType => |
|||
{ |
|||
if (!responseType.StartsWith(OpenIddictConstants.Permissions.Prefixes.ResponseType)) |
|||
{ |
|||
return OpenIddictConstants.Permissions.Prefixes.ResponseType + responseType; |
|||
} |
|||
return responseType; |
|||
})); |
|||
permissions.AddRange( |
|||
dto.Scopes.Select(scope => |
|||
{ |
|||
if (!scope.StartsWith(OpenIddictConstants.Permissions.Prefixes.Scope)) |
|||
{ |
|||
return OpenIddictConstants.Permissions.Prefixes.Scope + scope; |
|||
} |
|||
return scope; |
|||
})); |
|||
entity.Permissions = jsonSerializer.Serialize(permissions); |
|||
|
|||
foreach (var extraProperty in dto.ExtraProperties) |
|||
{ |
|||
entity.ExtraProperties.Remove(extraProperty.Key); |
|||
entity.ExtraProperties.Add(extraProperty.Key, extraProperty.Value); |
|||
} |
|||
|
|||
return entity; |
|||
} |
|||
|
|||
public static OpenIddictApplicationDto ToDto(this OpenIddictApplication entity, IJsonSerializer jsonSerializer) |
|||
{ |
|||
if (entity == null) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
var dto = new OpenIddictApplicationDto |
|||
{ |
|||
Id = entity.Id, |
|||
ClientId = entity.ClientId, |
|||
ClientSecret = entity.ClientSecret, |
|||
ConsentType = entity.ConsentType, |
|||
DisplayName = entity.DisplayName, |
|||
CreationTime = entity.CreationTime, |
|||
CreatorId = entity.CreatorId, |
|||
LastModificationTime = entity.LastModificationTime, |
|||
LastModifierId = entity.LastModifierId, |
|||
DisplayNames = jsonSerializer.DeserializeToDictionary<string, string>(entity.DisplayNames), |
|||
PostLogoutRedirectUris = jsonSerializer.DeserializeToList<string>(entity.PostLogoutRedirectUris), |
|||
Properties = jsonSerializer.DeserializeToDictionary<string, string>(entity.Properties), |
|||
RedirectUris = jsonSerializer.DeserializeToList<string>(entity.RedirectUris), |
|||
Type = entity.Type, |
|||
ClientUri = entity.ClientUri, |
|||
LogoUri = entity.LogoUri |
|||
}; |
|||
|
|||
var requirements = jsonSerializer.DeserializeToList<string>(entity.Requirements); |
|||
dto.Requirements = requirements |
|||
.Where(HasPrefixKey(OpenIddictConstants.Requirements.Prefixes.Feature)) |
|||
.Select(GetPrefixKey(OpenIddictConstants.Requirements.Prefixes.Feature)) |
|||
.ToList(); |
|||
|
|||
var permissions = jsonSerializer.DeserializeToList<string>(entity.Permissions); |
|||
|
|||
dto.Endpoints = permissions |
|||
.Where(HasPrefixKey(OpenIddictConstants.Permissions.Prefixes.Endpoint)) |
|||
.Select(GetPrefixKey(OpenIddictConstants.Permissions.Prefixes.Endpoint)) |
|||
.ToList(); |
|||
|
|||
dto.GrantTypes = permissions |
|||
.Where(HasPrefixKey(OpenIddictConstants.Permissions.Prefixes.GrantType)) |
|||
.Select(GetPrefixKey(OpenIddictConstants.Permissions.Prefixes.GrantType)) |
|||
.ToList(); |
|||
|
|||
dto.ResponseTypes = permissions |
|||
.Where(HasPrefixKey(OpenIddictConstants.Permissions.Prefixes.ResponseType)) |
|||
.Select(GetPrefixKey(OpenIddictConstants.Permissions.Prefixes.ResponseType)) |
|||
.ToList(); |
|||
|
|||
dto.Scopes = permissions |
|||
.Where(HasPrefixKey(OpenIddictConstants.Permissions.Prefixes.Scope)) |
|||
.Select(GetPrefixKey(OpenIddictConstants.Permissions.Prefixes.Scope)) |
|||
.ToList(); |
|||
|
|||
foreach (var extraProperty in entity.ExtraProperties) |
|||
{ |
|||
dto.ExtraProperties.Add(extraProperty.Key, extraProperty.Value); |
|||
} |
|||
|
|||
return dto; |
|||
} |
|||
|
|||
private static Func<string, bool> HasPrefixKey(string prefix) |
|||
{ |
|||
return p => p.StartsWith(prefix); |
|||
} |
|||
|
|||
private static Func<string, string> GetPrefixKey(string prefix) |
|||
{ |
|||
return p => p.RemovePreFix(prefix); |
|||
} |
|||
} |
|||
@ -0,0 +1,82 @@ |
|||
using LINGYUN.Abp.OpenIddict.Permissions; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using System; |
|||
using System.Linq; |
|||
using System.Linq.Dynamic.Core; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.OpenIddict.Authorizations; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Authorizations; |
|||
|
|||
[Authorize(AbpOpenIddictPermissions.Authorizations.Default)] |
|||
public class OpenIddictAuthorizationAppService : OpenIddictApplicationServiceBase, IOpenIddictAuthorizationAppService |
|||
{ |
|||
protected IRepository<OpenIddictAuthorization, Guid> Repository { get; } |
|||
|
|||
public OpenIddictAuthorizationAppService( |
|||
IRepository<OpenIddictAuthorization, Guid> repository) |
|||
{ |
|||
Repository = repository; |
|||
} |
|||
|
|||
[Authorize(AbpOpenIddictPermissions.Authorizations.Delete)] |
|||
public async virtual Task DeleteAsync(Guid id) |
|||
{ |
|||
await Repository.DeleteAsync(id); |
|||
|
|||
await CurrentUnitOfWork.SaveChangesAsync(); |
|||
} |
|||
|
|||
public async virtual Task<OpenIddictAuthorizationDto> GetAsync(Guid id) |
|||
{ |
|||
var authorization = await Repository.GetAsync(id); |
|||
|
|||
return authorization.ToDto(JsonSerializer); |
|||
} |
|||
|
|||
public async virtual Task<PagedResultDto<OpenIddictAuthorizationDto>> GetListAsync(OpenIddictAuthorizationGetListInput input) |
|||
{ |
|||
var queryable = await Repository.GetQueryableAsync(); |
|||
if (input.ClientId.HasValue) |
|||
{ |
|||
queryable = queryable.Where(x => x.ApplicationId == input.ClientId); |
|||
} |
|||
if (input.BeginCreationTime.HasValue) |
|||
{ |
|||
queryable = queryable.Where(x => x.CreationTime >= input.BeginCreationTime); |
|||
} |
|||
if (input.EndCreationTime.HasValue) |
|||
{ |
|||
queryable = queryable.Where(x => x.CreationTime <= input.BeginCreationTime); |
|||
} |
|||
if (!input.Status.IsNullOrWhiteSpace()) |
|||
{ |
|||
queryable = queryable.Where(x => x.Status == input.Status); |
|||
} |
|||
if (!input.Type.IsNullOrWhiteSpace()) |
|||
{ |
|||
queryable = queryable.Where(x => x.Type == input.Type); |
|||
} |
|||
if (!input.Subject.IsNullOrWhiteSpace()) |
|||
{ |
|||
queryable = queryable.Where(x => x.Subject == input.Subject); |
|||
} |
|||
if (!input.Filter.IsNullOrWhiteSpace()) |
|||
{ |
|||
queryable = queryable.Where(x => x.Subject.Contains(input.Filter) || |
|||
x.Status.Contains(input.Filter) || x.Type.Contains(input.Filter) || |
|||
x.Scopes.Contains(input.Filter) || x.Properties.Contains(input.Filter)); |
|||
} |
|||
queryable = queryable |
|||
.OrderBy(input.Sorting ?? $"{nameof(OpenIddictAuthorization.CreationTime)} DESC") |
|||
.PageBy(input.SkipCount, input.MaxResultCount); |
|||
|
|||
var totalCount = await AsyncExecuter.CountAsync(queryable); |
|||
var entites = await AsyncExecuter.ToListAsync(queryable); |
|||
|
|||
return new PagedResultDto<OpenIddictAuthorizationDto>(totalCount, |
|||
entites.Select(entity => entity.ToDto(JsonSerializer)).ToList()); |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
using Volo.Abp.Json; |
|||
using Volo.Abp.OpenIddict.Authorizations; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Authorizations; |
|||
|
|||
internal static class OpenIddictAuthorizationExtensions |
|||
{ |
|||
public static OpenIddictAuthorizationDto ToDto(this OpenIddictAuthorization entity, IJsonSerializer jsonSerializer) |
|||
{ |
|||
if (entity == null) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
var dto = new OpenIddictAuthorizationDto |
|||
{ |
|||
Id = entity.Id, |
|||
ApplicationId = entity.ApplicationId, |
|||
CreationDate = entity.CreationDate, |
|||
CreationTime = entity.CreationTime, |
|||
CreatorId = entity.CreatorId, |
|||
LastModificationTime = entity.LastModificationTime, |
|||
LastModifierId = entity.LastModifierId, |
|||
Properties = jsonSerializer.DeserializeToDictionary<string, string>(entity.Properties), |
|||
Scopes = jsonSerializer.DeserializeToList<string>(entity.Scopes), |
|||
Status = entity.Status, |
|||
Subject = entity.Subject, |
|||
Type = entity.Type |
|||
}; |
|||
|
|||
foreach (var extraProperty in entity.ExtraProperties) |
|||
{ |
|||
dto.ExtraProperties.Add(extraProperty.Key, extraProperty.Value); |
|||
} |
|||
|
|||
return dto; |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System.Threading; |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.OpenIddict.Localization; |
|||
using Volo.Abp.Threading; |
|||
using Volo.Abp.Json; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict; |
|||
|
|||
public abstract class OpenIddictApplicationServiceBase : ApplicationService |
|||
{ |
|||
protected IJsonSerializer JsonSerializer => LazyServiceProvider.LazyGetRequiredService<IJsonSerializer>(); |
|||
protected ICancellationTokenProvider CancellationTokenProvider => LazyServiceProvider.LazyGetRequiredService<ICancellationTokenProvider>(); |
|||
protected OpenIddictApplicationServiceBase() |
|||
{ |
|||
LocalizationResource = typeof(AbpOpenIddictResource); |
|||
} |
|||
|
|||
protected virtual CancellationToken GetCancellationToken() |
|||
{ |
|||
return CancellationTokenProvider.FallbackToProvider(); |
|||
} |
|||
} |
|||
@ -0,0 +1,85 @@ |
|||
using LINGYUN.Abp.OpenIddict.Permissions; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using System; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.OpenIddict.Scopes; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Scopes; |
|||
|
|||
[Authorize(AbpOpenIddictPermissions.Scopes.Default)] |
|||
public class OpenIddictScopeAppService : OpenIddictApplicationServiceBase, IOpenIddictScopeAppService |
|||
{ |
|||
protected IOpenIddictScopeRepository Repository { get; } |
|||
|
|||
public OpenIddictScopeAppService(IOpenIddictScopeRepository repository) |
|||
{ |
|||
Repository = repository; |
|||
} |
|||
|
|||
[Authorize(AbpOpenIddictPermissions.Scopes.Create)] |
|||
public async virtual Task<OpenIddictScopeDto> CreateAsync(OpenIddictScopeCreateDto input) |
|||
{ |
|||
if (await Repository.FindByNameAsync(input.Name) != null) |
|||
{ |
|||
throw new BusinessException(OpenIddictApplicationErrorCodes.Scopes.NameExisted) |
|||
.WithData(nameof(OpenIddictScope.Name), input.Name); |
|||
} |
|||
|
|||
var scope = new OpenIddictScope(GuidGenerator.Create()); |
|||
scope = input.ToEntity(scope, JsonSerializer); |
|||
|
|||
scope = await Repository.InsertAsync(scope); |
|||
|
|||
await CurrentUnitOfWork.SaveChangesAsync(); |
|||
|
|||
return scope.ToDto(JsonSerializer); |
|||
} |
|||
|
|||
[Authorize(AbpOpenIddictPermissions.Scopes.Delete)] |
|||
public async virtual Task DeleteAsync(Guid id) |
|||
{ |
|||
await Repository.DeleteAsync(id); |
|||
|
|||
await CurrentUnitOfWork.SaveChangesAsync(); |
|||
} |
|||
|
|||
public async virtual Task<OpenIddictScopeDto> GetAsync(Guid id) |
|||
{ |
|||
var scope = await Repository.GetAsync(id); |
|||
|
|||
return scope.ToDto(JsonSerializer); |
|||
} |
|||
|
|||
public async virtual Task<PagedResultDto<OpenIddictScopeDto>> GetListAsync(OpenIddictScopeGetListInput input) |
|||
{ |
|||
var totalCount = await Repository.GetCountAsync(input.Filter); |
|||
var entites = await Repository.GetListAsync(input.Sorting, input.SkipCount, input.MaxResultCount, input.Filter); |
|||
|
|||
return new PagedResultDto<OpenIddictScopeDto>(totalCount, |
|||
entites.Select(entity => entity.ToDto(JsonSerializer)).ToList()); |
|||
} |
|||
|
|||
[Authorize(AbpOpenIddictPermissions.Scopes.Update)] |
|||
public async virtual Task<OpenIddictScopeDto> UpdateAsync(Guid id, OpenIddictScopeUpdateDto input) |
|||
{ |
|||
var scope = await Repository.GetAsync(id); |
|||
|
|||
if (!string.Equals(scope.Name, input.Name) && |
|||
await Repository.FindByNameAsync(input.Name) != null) |
|||
{ |
|||
throw new BusinessException(OpenIddictApplicationErrorCodes.Scopes.NameExisted) |
|||
.WithData(nameof(OpenIddictScope.Name), input.Name); |
|||
} |
|||
|
|||
scope = input.ToEntity(scope, JsonSerializer); |
|||
|
|||
scope = await Repository.UpdateAsync(scope); |
|||
|
|||
await CurrentUnitOfWork.SaveChangesAsync(); |
|||
|
|||
return scope.ToDto(JsonSerializer); |
|||
} |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
using Volo.Abp; |
|||
using Volo.Abp.Json; |
|||
using Volo.Abp.OpenIddict.Scopes; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Scopes; |
|||
|
|||
internal static class OpenIddictScopeExtensions |
|||
{ |
|||
public static OpenIddictScope ToEntity(this OpenIddictScopeCreateOrUpdateDto dto, OpenIddictScope entity, IJsonSerializer jsonSerializer) |
|||
{ |
|||
Check.NotNull(dto, nameof(dto)); |
|||
Check.NotNull(entity, nameof(entity)); |
|||
|
|||
entity.Description = dto.Description; |
|||
entity.Descriptions = jsonSerializer.Serialize(dto.Descriptions); |
|||
entity.DisplayName = dto.DisplayName; |
|||
entity.DisplayNames = jsonSerializer.Serialize(dto.DisplayNames); |
|||
entity.Name = dto.Name; |
|||
entity.Properties = jsonSerializer.Serialize(dto.Properties); |
|||
entity.Resources = jsonSerializer.Serialize(dto.Resources); |
|||
|
|||
foreach (var extraProperty in dto.ExtraProperties) |
|||
{ |
|||
entity.ExtraProperties.Remove(extraProperty.Key); |
|||
entity.ExtraProperties.Add(extraProperty.Key, extraProperty.Value); |
|||
} |
|||
|
|||
return entity; |
|||
} |
|||
|
|||
public static OpenIddictScopeDto ToDto(this OpenIddictScope entity, IJsonSerializer jsonSerializer) |
|||
{ |
|||
if (entity == null) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
var dto = new OpenIddictScopeDto |
|||
{ |
|||
Id = entity.Id, |
|||
CreationTime = entity.CreationTime, |
|||
CreatorId = entity.CreatorId, |
|||
LastModificationTime = entity.LastModificationTime, |
|||
LastModifierId = entity.LastModifierId, |
|||
Description = entity.Description, |
|||
Descriptions = jsonSerializer.DeserializeToDictionary<string, string>(entity.Descriptions), |
|||
DisplayName = entity.DisplayName, |
|||
DisplayNames = jsonSerializer.DeserializeToDictionary<string, string>(entity.DisplayNames), |
|||
Name = entity.Name, |
|||
Properties = jsonSerializer.DeserializeToDictionary<string, string>(entity.Properties), |
|||
Resources = jsonSerializer.DeserializeToList<string>(entity.Resources) |
|||
}; |
|||
|
|||
|
|||
foreach (var extraProperty in entity.ExtraProperties) |
|||
{ |
|||
dto.ExtraProperties.Add(extraProperty.Key, extraProperty.Value); |
|||
} |
|||
|
|||
return dto; |
|||
} |
|||
} |
|||
@ -0,0 +1,94 @@ |
|||
using LINGYUN.Abp.OpenIddict.Permissions; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using System; |
|||
using System.Linq; |
|||
using System.Linq.Dynamic.Core; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.OpenIddict.Tokens; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Tokens; |
|||
|
|||
[Authorize(AbpOpenIddictPermissions.Tokens.Default)] |
|||
public class OpenIddictTokenAppService : OpenIddictApplicationServiceBase, IOpenIddictTokenAppService |
|||
{ |
|||
protected IRepository<OpenIddictToken, Guid> Repository { get; } |
|||
|
|||
public OpenIddictTokenAppService(IRepository<OpenIddictToken, Guid> repository) |
|||
{ |
|||
Repository = repository; |
|||
} |
|||
|
|||
[Authorize(AbpOpenIddictPermissions.Tokens.Delete)] |
|||
public async virtual Task DeleteAsync(Guid id) |
|||
{ |
|||
await Repository.DeleteAsync(id); |
|||
|
|||
await CurrentUnitOfWork.SaveChangesAsync(); |
|||
} |
|||
|
|||
public async virtual Task<OpenIddictTokenDto> GetAsync(Guid id) |
|||
{ |
|||
var scope = await Repository.GetAsync(id); |
|||
|
|||
return scope.ToDto(); |
|||
} |
|||
|
|||
public async virtual Task<PagedResultDto<OpenIddictTokenDto>> GetListAsync(OpenIddictTokenGetListInput input) |
|||
{ |
|||
var queryable = await Repository.GetQueryableAsync(); |
|||
if (input.ClientId.HasValue) |
|||
{ |
|||
queryable = queryable.Where(x => x.ApplicationId == input.ClientId); |
|||
} |
|||
if (input.BeginCreationTime.HasValue) |
|||
{ |
|||
queryable = queryable.Where(x => x.CreationTime >= input.BeginCreationTime); |
|||
} |
|||
if (input.EndCreationTime.HasValue) |
|||
{ |
|||
queryable = queryable.Where(x => x.CreationTime <= input.BeginCreationTime); |
|||
} |
|||
if (input.BeginExpirationDate.HasValue) |
|||
{ |
|||
queryable = queryable.Where(x => x.ExpirationDate >= input.BeginCreationTime); |
|||
} |
|||
if (input.EndExpirationDate.HasValue) |
|||
{ |
|||
queryable = queryable.Where(x => x.ExpirationDate <= input.BeginCreationTime); |
|||
} |
|||
if (!input.Status.IsNullOrWhiteSpace()) |
|||
{ |
|||
queryable = queryable.Where(x => x.Status == input.Status); |
|||
} |
|||
if (!input.Type.IsNullOrWhiteSpace()) |
|||
{ |
|||
queryable = queryable.Where(x => x.Type == input.Type); |
|||
} |
|||
if (!input.Subject.IsNullOrWhiteSpace()) |
|||
{ |
|||
queryable = queryable.Where(x => x.Subject == input.Subject); |
|||
} |
|||
if (!input.ReferenceId.IsNullOrWhiteSpace()) |
|||
{ |
|||
queryable = queryable.Where(x => x.ReferenceId == input.ReferenceId); |
|||
} |
|||
if (!input.Filter.IsNullOrWhiteSpace()) |
|||
{ |
|||
queryable = queryable.Where(x => x.Subject.Contains(input.Filter) || |
|||
x.Status.Contains(input.Filter) || x.Type.Contains(input.Filter) || |
|||
x.Payload.Contains(input.Filter) || x.Properties.Contains(input.Filter) || |
|||
x.ReferenceId.Contains(input.ReferenceId)); |
|||
} |
|||
queryable = queryable |
|||
.OrderBy(input.Sorting ?? $"{nameof(OpenIddictToken.CreationTime)} DESC") |
|||
.PageBy(input.SkipCount, input.MaxResultCount); |
|||
|
|||
var totalCount = await AsyncExecuter.CountAsync(queryable); |
|||
var entites = await AsyncExecuter.ToListAsync(queryable); |
|||
|
|||
return new PagedResultDto<OpenIddictTokenDto>(totalCount, |
|||
entites.Select(entity => entity.ToDto()).ToList()); |
|||
} |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
using Volo.Abp; |
|||
using Volo.Abp.OpenIddict.Tokens; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Tokens; |
|||
|
|||
internal static class OpenIddictTokenExtensions |
|||
{ |
|||
public static OpenIddictToken ToEntity(this OpenIddictTokenDto dto, OpenIddictToken entity) |
|||
{ |
|||
Check.NotNull(dto, nameof(dto)); |
|||
Check.NotNull(entity, nameof(entity)); |
|||
|
|||
entity.ApplicationId = dto.ApplicationId; |
|||
entity.AuthorizationId = dto.AuthorizationId; |
|||
entity.CreationDate = dto.CreationDate; |
|||
entity.ExpirationDate = dto.ExpirationDate; |
|||
entity.Payload = dto.Payload; |
|||
entity.Properties = dto.Properties; |
|||
entity.RedemptionDate = dto.RedemptionDate; |
|||
entity.ReferenceId = dto.ReferenceId; |
|||
entity.Status = dto.Status; |
|||
entity.Subject = dto.Subject; |
|||
entity.Type = dto.Type; |
|||
|
|||
foreach (var extraProperty in dto.ExtraProperties) |
|||
{ |
|||
entity.ExtraProperties.Remove(extraProperty.Key); |
|||
entity.ExtraProperties.Add(extraProperty.Key, extraProperty.Value); |
|||
} |
|||
|
|||
return entity; |
|||
} |
|||
|
|||
public static OpenIddictTokenDto ToDto(this OpenIddictToken entity) |
|||
{ |
|||
if (entity == null) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
var model = new OpenIddictTokenDto |
|||
{ |
|||
Id = entity.Id, |
|||
ApplicationId = entity.ApplicationId, |
|||
AuthorizationId = entity.AuthorizationId, |
|||
CreationDate = entity.CreationDate, |
|||
CreationTime = entity.CreationTime, |
|||
CreatorId = entity.CreatorId, |
|||
LastModificationTime = entity.LastModificationTime, |
|||
LastModifierId = entity.LastModifierId, |
|||
ExpirationDate = entity.ExpirationDate, |
|||
Payload = entity.Payload, |
|||
Properties = entity.Properties, |
|||
RedemptionDate = entity.RedemptionDate, |
|||
ReferenceId = entity.ReferenceId, |
|||
Status = entity.Status, |
|||
Subject = entity.Subject, |
|||
Type = entity.Type |
|||
}; |
|||
|
|||
foreach (var extraProperty in entity.ExtraProperties) |
|||
{ |
|||
model.ExtraProperties.Add(extraProperty.Key, extraProperty.Value); |
|||
} |
|||
|
|||
return model; |
|||
} |
|||
} |
|||
@ -0,0 +1,26 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Volo.Abp.Json; |
|||
internal static class IJsonSerializerExtensions |
|||
{ |
|||
public static List<TResult> DeserializeToList<TResult>(this IJsonSerializer serializer, string source) |
|||
{ |
|||
if (source.IsNullOrWhiteSpace()) |
|||
{ |
|||
return new List<TResult>(); |
|||
} |
|||
|
|||
return serializer.Deserialize<List<TResult>>(source); |
|||
} |
|||
|
|||
public static Dictionary<TKey, TValue> DeserializeToDictionary<TKey, TValue>(this IJsonSerializer serializer, string source) |
|||
{ |
|||
if (source.IsNullOrWhiteSpace()) |
|||
{ |
|||
return new Dictionary<TKey, TValue>(); |
|||
} |
|||
|
|||
return serializer.Deserialize<Dictionary<TKey, TValue>>(source); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,16 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.props" /> |
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\..\dapr\LINGYUN.Abp.Dapr.Client\LINGYUN.Abp.Dapr.Client.csproj" /> |
|||
<ProjectReference Include="..\LINGYUN.Abp.OpenIddict.Application.Contracts\LINGYUN.Abp.OpenIddict.Application.Contracts.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,18 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using LINGYUN.Abp.Dapr.Client; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpOpenIddictApplicationContractsModule), |
|||
typeof(AbpDaprClientModule))] |
|||
public class AbpOpenIddictDaprClientModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddStaticDaprClientProxies( |
|||
typeof(AbpOpenIddictApplicationContractsModule).Assembly, |
|||
OpenIddictRemoteServiceConsts.RemoteServiceName); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,19 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.props" /> |
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>netstandard2.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.Http.Client" Version="$(VoloAbpPackageVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.OpenIddict.Application.Contracts\LINGYUN.Abp.OpenIddict.Application.Contracts.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,18 @@ |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.Http.Client; |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpOpenIddictApplicationContractsModule), |
|||
typeof(AbpHttpClientModule))] |
|||
public class AbpOpenIddictHttpApiClientModule : AbpModule |
|||
{ |
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
context.Services.AddStaticHttpClientProxies( |
|||
typeof(AbpOpenIddictApplicationContractsModule).Assembly, |
|||
OpenIddictRemoteServiceConsts.RemoteServiceName); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,19 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.props" /> |
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.AspNetCore.Mvc" Version="$(VoloAbpPackageVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<ProjectReference Include="..\LINGYUN.Abp.OpenIddict.Application.Contracts\LINGYUN.Abp.OpenIddict.Application.Contracts.csproj" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,38 @@ |
|||
using Localization.Resources.AbpUi; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.AspNetCore.Mvc.Localization; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.OpenIddict.Localization; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpOpenIddictApplicationContractsModule), |
|||
typeof(AbpAspNetCoreMvcModule))] |
|||
public class AbpOpenIddictHttpApiModule : AbpModule |
|||
{ |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
PreConfigure<AbpMvcDataAnnotationsLocalizationOptions>(options => |
|||
{ |
|||
options.AddAssemblyResource(typeof(AbpOpenIddictResource), typeof(AbpOpenIddictApplicationContractsModule).Assembly); |
|||
}); |
|||
|
|||
PreConfigure<IMvcBuilder>(mvcBuilder => |
|||
{ |
|||
mvcBuilder.AddApplicationPartIfNotExists(typeof(AbpOpenIddictHttpApiModule).Assembly); |
|||
}); |
|||
} |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpLocalizationOptions>(options => |
|||
{ |
|||
options.Resources |
|||
.Get<AbpOpenIddictResource>() |
|||
.AddBaseTypes(typeof(AbpUiResource)); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
using LINGYUN.Abp.OpenIddict.Permissions; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Applications; |
|||
|
|||
[Controller] |
|||
[Authorize(AbpOpenIddictPermissions.Applications.Default)] |
|||
[RemoteService(Name = OpenIddictRemoteServiceConsts.RemoteServiceName)] |
|||
[Area(OpenIddictRemoteServiceConsts.ModuleName)] |
|||
[Route("api/openiddict/applications")] |
|||
public class OpenIddictApplicationController : OpenIddictControllerBase, IOpenIddictApplicationAppService |
|||
{ |
|||
protected IOpenIddictApplicationAppService Service { get; } |
|||
|
|||
public OpenIddictApplicationController(IOpenIddictApplicationAppService service) |
|||
{ |
|||
Service = service; |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("{id}")] |
|||
public virtual Task<OpenIddictApplicationDto> GetAsync(Guid id) |
|||
{ |
|||
return Service.GetAsync(id); |
|||
} |
|||
|
|||
[HttpGet] |
|||
public virtual Task<PagedResultDto<OpenIddictApplicationDto>> GetListAsync(OpenIddictApplicationGetListInput input) |
|||
{ |
|||
return Service.GetListAsync(input); |
|||
} |
|||
|
|||
[HttpPost] |
|||
[Authorize(AbpOpenIddictPermissions.Applications.Create)] |
|||
public virtual Task<OpenIddictApplicationDto> CreateAsync(OpenIddictApplicationCreateDto input) |
|||
{ |
|||
return Service.CreateAsync(input); |
|||
} |
|||
|
|||
[HttpPut] |
|||
[Route("{id}")] |
|||
[Authorize(AbpOpenIddictPermissions.Applications.Update)] |
|||
public virtual Task<OpenIddictApplicationDto> UpdateAsync(Guid id, OpenIddictApplicationUpdateDto input) |
|||
{ |
|||
return Service.UpdateAsync(id, input); |
|||
} |
|||
|
|||
[HttpDelete] |
|||
[Route("{id}")] |
|||
[Authorize(AbpOpenIddictPermissions.Applications.Delete)] |
|||
public virtual Task DeleteAsync(Guid id) |
|||
{ |
|||
return Service.DeleteAsync(id); |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
using LINGYUN.Abp.OpenIddict.Permissions; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Authorizations; |
|||
|
|||
[Controller] |
|||
[Authorize(AbpOpenIddictPermissions.Authorizations.Default)] |
|||
[RemoteService(Name = OpenIddictRemoteServiceConsts.RemoteServiceName)] |
|||
[Area(OpenIddictRemoteServiceConsts.ModuleName)] |
|||
[Route("api/openiddict/authorizations")] |
|||
public class OpenIddictAuthorizationController : OpenIddictControllerBase, IOpenIddictAuthorizationAppService |
|||
{ |
|||
protected IOpenIddictAuthorizationAppService Service { get; } |
|||
|
|||
public OpenIddictAuthorizationController(IOpenIddictAuthorizationAppService service) |
|||
{ |
|||
Service = service; |
|||
} |
|||
|
|||
[HttpDelete] |
|||
[Route("{id}")] |
|||
[Authorize(AbpOpenIddictPermissions.Authorizations.Delete)] |
|||
public virtual Task DeleteAsync(Guid id) |
|||
{ |
|||
return Service.DeleteAsync(id); |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("{id}")] |
|||
public virtual Task<OpenIddictAuthorizationDto> GetAsync(Guid id) |
|||
{ |
|||
return Service.GetAsync(id); |
|||
} |
|||
|
|||
[HttpGet] |
|||
public virtual Task<PagedResultDto<OpenIddictAuthorizationDto>> GetListAsync(OpenIddictAuthorizationGetListInput input) |
|||
{ |
|||
return Service.GetListAsync(input); |
|||
} |
|||
} |
|||
@ -0,0 +1,12 @@ |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
using Volo.Abp.OpenIddict.Localization; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict; |
|||
|
|||
public abstract class OpenIddictControllerBase : AbpControllerBase |
|||
{ |
|||
protected OpenIddictControllerBase() |
|||
{ |
|||
LocalizationResource = typeof(AbpOpenIddictResource); |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
using LINGYUN.Abp.OpenIddict.Permissions; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Scopes; |
|||
|
|||
[Controller] |
|||
[Authorize(AbpOpenIddictPermissions.Scopes.Default)] |
|||
[RemoteService(Name = OpenIddictRemoteServiceConsts.RemoteServiceName)] |
|||
[Area(OpenIddictRemoteServiceConsts.ModuleName)] |
|||
[Route("api/openiddict/scopes")] |
|||
public class OpenIddictScopeController : OpenIddictControllerBase, IOpenIddictScopeAppService |
|||
{ |
|||
protected IOpenIddictScopeAppService Service { get; } |
|||
|
|||
public OpenIddictScopeController(IOpenIddictScopeAppService service) |
|||
{ |
|||
Service = service; |
|||
} |
|||
|
|||
[HttpPost] |
|||
[Authorize(AbpOpenIddictPermissions.Scopes.Create)] |
|||
public virtual Task<OpenIddictScopeDto> CreateAsync(OpenIddictScopeCreateDto input) |
|||
{ |
|||
return Service.CreateAsync(input); |
|||
} |
|||
|
|||
[HttpDelete] |
|||
[Route("{id}")] |
|||
[Authorize(AbpOpenIddictPermissions.Scopes.Delete)] |
|||
public virtual Task DeleteAsync(Guid id) |
|||
{ |
|||
return Service.DeleteAsync(id); |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("{id}")] |
|||
public virtual Task<OpenIddictScopeDto> GetAsync(Guid id) |
|||
{ |
|||
return Service.GetAsync(id); |
|||
} |
|||
|
|||
[HttpGet] |
|||
public virtual Task<PagedResultDto<OpenIddictScopeDto>> GetListAsync(OpenIddictScopeGetListInput input) |
|||
{ |
|||
return Service.GetListAsync(input); |
|||
} |
|||
|
|||
[HttpPut] |
|||
[Route("{id}")] |
|||
[Authorize(AbpOpenIddictPermissions.Scopes.Update)] |
|||
public virtual Task<OpenIddictScopeDto> UpdateAsync(Guid id, OpenIddictScopeUpdateDto input) |
|||
{ |
|||
return Service.UpdateAsync(id, input); |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
using LINGYUN.Abp.OpenIddict.Permissions; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.Tokens; |
|||
|
|||
[Controller] |
|||
[Authorize(AbpOpenIddictPermissions.Tokens.Default)] |
|||
[RemoteService(Name = OpenIddictRemoteServiceConsts.RemoteServiceName)] |
|||
[Area(OpenIddictRemoteServiceConsts.ModuleName)] |
|||
[Route("api/openiddict/tokens")] |
|||
public class OpenIddictTokenController : OpenIddictControllerBase, IOpenIddictTokenAppService |
|||
{ |
|||
protected IOpenIddictTokenAppService Service { get; } |
|||
|
|||
public OpenIddictTokenController(IOpenIddictTokenAppService service) |
|||
{ |
|||
Service = service; |
|||
} |
|||
|
|||
[HttpDelete] |
|||
[Route("{id}")] |
|||
[Authorize(AbpOpenIddictPermissions.Tokens.Delete)] |
|||
public virtual Task DeleteAsync(Guid id) |
|||
{ |
|||
return Service.DeleteAsync(id); |
|||
} |
|||
|
|||
[HttpGet] |
|||
[Route("{id}")] |
|||
public virtual Task<OpenIddictTokenDto> GetAsync(Guid id) |
|||
{ |
|||
return Service.GetAsync(id); |
|||
} |
|||
|
|||
[HttpGet] |
|||
public virtual Task<PagedResultDto<OpenIddictTokenDto>> GetListAsync(OpenIddictTokenGetListInput input) |
|||
{ |
|||
return Service.GetListAsync(input); |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -0,0 +1,30 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"> |
|||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. --> |
|||
<xs:element name="Weavers"> |
|||
<xs:complexType> |
|||
<xs:all> |
|||
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1"> |
|||
<xs:complexType> |
|||
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" /> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:all> |
|||
<xs:attribute name="VerifyAssembly" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string"> |
|||
<xs:annotation> |
|||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
<xs:attribute name="GenerateXsd" type="xs:boolean"> |
|||
<xs:annotation> |
|||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation> |
|||
</xs:annotation> |
|||
</xs:attribute> |
|||
</xs:complexType> |
|||
</xs:element> |
|||
</xs:schema> |
|||
@ -0,0 +1,23 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk"> |
|||
|
|||
<Import Project="..\..\..\common.props" /> |
|||
<Import Project="..\..\..\configureawait.props" /> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net6.0</TargetFramework> |
|||
<RootNamespace /> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<None Remove="LINGYUN\Abp\OpenIddict\LinkUser\Localization\Resources\*.json" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<EmbeddedResource Include="LINGYUN\Abp\OpenIddict\LinkUser\Localization\Resources\*.json" /> |
|||
</ItemGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Volo.Abp.OpenIddict.AspNetCore" Version="$(VoloAbpPackageVersion)" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
|||
@ -0,0 +1,45 @@ |
|||
using LINGYUN.Abp.OpenIddict.LinkUser.Controllers; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Volo.Abp.Localization; |
|||
using Volo.Abp.Modularity; |
|||
using Volo.Abp.OpenIddict; |
|||
using Volo.Abp.OpenIddict.ExtensionGrantTypes; |
|||
using Volo.Abp.OpenIddict.Localization; |
|||
using Volo.Abp.VirtualFileSystem; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.LinkUser; |
|||
|
|||
[DependsOn( |
|||
typeof(AbpOpenIddictAspNetCoreModule))] |
|||
public class AbpOpenIddictLinkUserModule : AbpModule |
|||
{ |
|||
public override void PreConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
PreConfigure<OpenIddictServerBuilder>(builder => |
|||
{ |
|||
builder.AllowLinkUserFlow(); |
|||
}); |
|||
} |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
Configure<AbpOpenIddictExtensionGrantsOptions>(options => |
|||
{ |
|||
options.Grants.TryAdd( |
|||
LinkUserTokenExtensionGrantConsts.GrantType, |
|||
new LinkUserTokenController()); |
|||
}); |
|||
|
|||
Configure<AbpVirtualFileSystemOptions>(options => |
|||
{ |
|||
options.FileSets.AddEmbedded<AbpOpenIddictLinkUserModule>(); |
|||
}); |
|||
|
|||
Configure<AbpLocalizationOptions>(options => |
|||
{ |
|||
options.Resources |
|||
.Get<AbpOpenIddictResource>() |
|||
.AddVirtualJson("/LINGYUN/Abp/OpenIddict/LinkUser/Localization/Resources"); |
|||
}); |
|||
} |
|||
} |
|||
@ -0,0 +1,164 @@ |
|||
using Microsoft.AspNetCore.Authentication; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Logging; |
|||
using OpenIddict.Abstractions; |
|||
using OpenIddict.Server.AspNetCore; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Identity; |
|||
using Volo.Abp.OpenIddict; |
|||
using Volo.Abp.OpenIddict.Controllers; |
|||
using Volo.Abp.OpenIddict.ExtensionGrantTypes; |
|||
using Volo.Abp.Security.Claims; |
|||
|
|||
namespace LINGYUN.Abp.OpenIddict.LinkUser.Controllers; |
|||
|
|||
[IgnoreAntiforgeryToken] |
|||
[ApiExplorerSettings(IgnoreApi = true)] |
|||
public class LinkUserTokenController : AbpOpenIdDictControllerBase, ITokenExtensionGrant |
|||
{ |
|||
public string Name => LinkUserTokenExtensionGrantConsts.GrantType; |
|||
|
|||
protected ICurrentPrincipalAccessor CurrentPrincipalAccessor => LazyServiceProvider.LazyGetRequiredService<ICurrentPrincipalAccessor>(); |
|||
protected IdentityLinkUserManager IdentityLinkUserManager => LazyServiceProvider.LazyGetRequiredService<IdentityLinkUserManager>(); |
|||
protected IdentitySecurityLogManager IdentitySecurityLogManager => LazyServiceProvider.LazyGetRequiredService<IdentitySecurityLogManager>(); |
|||
|
|||
public async virtual Task<IActionResult> HandleAsync(ExtensionGrantContext context) |
|||
{ |
|||
LazyServiceProvider = context.HttpContext.RequestServices.GetRequiredService<IAbpLazyServiceProvider>(); |
|||
|
|||
var accessTokenParam = context.Request.GetParameter("access_token"); |
|||
var accessToken = accessTokenParam.ToString(); |
|||
if (accessToken.IsNullOrWhiteSpace()) |
|||
{ |
|||
var properties = new AuthenticationProperties(new Dictionary<string, string> |
|||
{ |
|||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant, |
|||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = L["InvalidAccessToken"] |
|||
}); |
|||
|
|||
return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
|
|||
await foreach (var result in TokenManager.ValidateAsync(accessToken)) |
|||
{ |
|||
var properties = new AuthenticationProperties(new Dictionary<string, string> |
|||
{ |
|||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant, |
|||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = result.ErrorMessage |
|||
}); |
|||
|
|||
return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
|
|||
var userId = await TokenManager.GetIdAsync(accessToken); |
|||
var user = await UserManager.FindByIdAsync(userId); |
|||
var principal = await SignInManager.CreateUserPrincipalAsync(user); |
|||
|
|||
principal.SetScopes(context.Request.GetScopes()); |
|||
principal.SetResources(await GetResourcesAsync(context.Request.GetScopes())); |
|||
|
|||
await SetClaimsDestinationsAsync(principal); |
|||
|
|||
using (CurrentPrincipalAccessor.Change(principal)) |
|||
{ |
|||
var linkUserIdParam = context.Request.GetParameter("LinkUserId"); |
|||
if (!linkUserIdParam.HasValue || !Guid.TryParse(linkUserIdParam.Value.ToString(), out var linkUserId)) |
|||
{ |
|||
var properties = new AuthenticationProperties(new Dictionary<string, string> |
|||
{ |
|||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant, |
|||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = L["InvalidLinkUserId"] |
|||
}); |
|||
|
|||
return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
|
|||
Guid? linkTenantId = null; |
|||
var linkTenantIdParam = context.Request.GetParameter("LinkTenantId"); |
|||
if (linkTenantIdParam.HasValue) |
|||
{ |
|||
if (!Guid.TryParse(linkTenantIdParam.Value.ToString(), out var parsedGuid)) |
|||
{ |
|||
var properties = new AuthenticationProperties(new Dictionary<string, string> |
|||
{ |
|||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant, |
|||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = L["InvalidLinkTenantId"] |
|||
}); |
|||
|
|||
return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
|
|||
linkTenantId = parsedGuid; |
|||
} |
|||
|
|||
var isLinked = await IdentityLinkUserManager.IsLinkedAsync( |
|||
new IdentityLinkUserInfo(user.Id, CurrentTenant.Id), |
|||
new IdentityLinkUserInfo(linkUserId, linkTenantId)); |
|||
|
|||
if (isLinked) |
|||
{ |
|||
using (CurrentTenant.Change(linkTenantId)) |
|||
{ |
|||
var linkUser = await UserManager.GetByIdAsync(linkUserId); |
|||
|
|||
return await SetSuccessResultAsync(context, linkUser); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
var properties = new AuthenticationProperties(new Dictionary<string, string> |
|||
{ |
|||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant, |
|||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = L["TheTargetUserIsNotLinkedToYou"] |
|||
}); |
|||
|
|||
return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
} |
|||
} |
|||
|
|||
protected virtual async Task<IActionResult> SetSuccessResultAsync(ExtensionGrantContext context, IdentityUser user) |
|||
{ |
|||
Logger.LogInformation("Credentials validated for username: {username}", user.UserName); |
|||
|
|||
var principal = await SignInManager.CreateUserPrincipalAsync(user); |
|||
|
|||
principal.SetScopes(context.Request.GetScopes()); |
|||
principal.SetResources(await GetResourcesAsync(context.Request.GetScopes())); |
|||
|
|||
await SetClaimsDestinationsAsync(principal); |
|||
|
|||
await SaveSecurityLogAsync( |
|||
context, |
|||
user, |
|||
OpenIddictSecurityLogActionConsts.LoginSucceeded); |
|||
|
|||
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
|
|||
protected async virtual Task SaveSecurityLogAsync( |
|||
ExtensionGrantContext context, |
|||
IdentityUser user, |
|||
string action) |
|||
{ |
|||
var logContext = new IdentitySecurityLogContext |
|||
{ |
|||
Identity = OpenIddictSecurityLogIdentityConsts.OpenIddict, |
|||
Action = action, |
|||
UserName = user.UserName, |
|||
ClientId = await FindClientIdAsync(context) |
|||
}; |
|||
logContext.WithProperty("GrantType", Name); |
|||
|
|||
await IdentitySecurityLogManager.SaveAsync(logContext); |
|||
} |
|||
|
|||
protected virtual Task<string> FindClientIdAsync(ExtensionGrantContext context) |
|||
{ |
|||
return Task.FromResult(context.Request.ClientId); |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue