Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 35 additions & 7 deletions src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ protected override async Task<OAuthTokenResponse> ExchangeCodeAsync([NotNull] OA
using var document = await JsonDocument.ParseAsync(stream);

var mainElement = document.RootElement.GetProperty("alipay_system_oauth_token_response");
if (!ValidateReturnCode(mainElement, out var code))
if (!ValidateReturnCode(mainElement, out var code, out var subCode))
{
return OAuthTokenResponse.Failed(new Exception($"An error (Code:{code}) occurred while retrieving an access token."));
return OAuthTokenResponse.Failed(new Exception($"An error (Code:{code} subCode:{subCode}) occurred while retrieving an access token."));
}

var payload = JsonDocument.Parse(mainElement.GetRawText());
Expand Down Expand Up @@ -126,15 +126,16 @@ protected override async Task<AuthenticationTicket> CreateTicketAsync(
if (!rootElement.TryGetProperty("alipay_user_info_share_response", out JsonElement mainElement))
{
var errorCode = rootElement.GetProperty("error_response").GetProperty("code").GetString()!;
throw new AuthenticationFailureException($"An error (Code:{errorCode}) occurred while retrieving user information.");
var errorSubCode = rootElement.GetProperty("error_response").GetProperty("sub_code").GetString()!;
throw new AuthenticationFailureException($"An error response (Code:{errorCode} subCode:{errorSubCode}) occurred while retrieving user information.");
}

if (!ValidateReturnCode(mainElement, out var code))
if (!ValidateReturnCode(mainElement, out var code, out var subCode))
{
throw new AuthenticationFailureException($"An error (Code:{code}) occurred while retrieving user information.");
throw new AuthenticationFailureException($"An error (Code:{code} subCode:{subCode}) occurred while retrieving user information.");
}

identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, mainElement.GetString("user_id")!, ClaimValueTypes.String, Options.ClaimsIssuer));
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, GetUserIdentifier(mainElement), ClaimValueTypes.String, Options.ClaimsIssuer));

var principal = new ClaimsPrincipal(identity);
var context = new OAuthCreatingTicketContext(principal, properties, Context, Scheme, Options, Backchannel, tokens, mainElement);
Expand All @@ -153,17 +154,28 @@ protected override async Task<AuthenticationTicket> CreateTicketAsync(
/// </summary>
/// <param name="element">Main part of json document from response</param>
/// <param name="code">Returned code from server</param>
/// <param name="subCode">Returned sub_code from server</param>
/// <remarks>See https://opendocs.alipay.com/open/common/105806 for details.</remarks>
/// <returns>True if succeed, otherwise false.</returns>
private static bool ValidateReturnCode(JsonElement element, out string code)
private static bool ValidateReturnCode(JsonElement element, out string code, out string subCode)
{
if (!element.TryGetProperty("code", out JsonElement codeElement))
{
code = string.Empty;
subCode = string.Empty;
return true;
}

code = codeElement.GetString()!;

if (!element.TryGetProperty("sub_code", out JsonElement subCodeElement))
{
subCode = string.Empty;
return true;
}

subCode = subCodeElement.GetString()!;

return code == "10000";
}

Expand Down Expand Up @@ -200,6 +212,22 @@ private string GetRSA2Signature([NotNull] SortedDictionary<string, string?> sort
return Convert.ToBase64String(encryptedBytes);
}

/// <summary>
/// Get user identifier from response.
/// </summary>
/// <param name="element">Main part of json document from response</param>
/// <remarks>See https://opendocs.alipay.com/common/0ai2i6?pathHash=cba76ebf for details.</remarks>
/// <returns>UserId or OpenId</returns>
private static string GetUserIdentifier(JsonElement element)
{
if (element.TryGetProperty("user_id", out JsonElement userIdElement))
{
return userIdElement.GetString()!;
}

return element.GetString("open_id")!;
}

/// <inheritdoc />
protected override string BuildChallengeUrl([NotNull] AuthenticationProperties properties, [NotNull] string redirectUri)
{
Expand Down
40 changes: 40 additions & 0 deletions test/AspNet.Security.OAuth.Providers.Tests/Alipay/AlipayTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,46 @@ public async Task BuildChallengeUrl_Generates_Correct_Url(bool usePkce)
}
}

[Fact]
public async Task Cannot_Sign_In_Using_Alipay_With_Sub_Code_Error()
{
// Arrange
static void ConfigureServices(IServiceCollection services)
{
services.PostConfigureAll<AlipayAuthenticationOptions>((options) => options.ClientId = "error-id");
}

using var server = CreateTestServer(ConfigureServices);

// Act
var exception = await Assert.ThrowsAsync<AuthenticationFailureException>(async () => await AuthenticateUserAsync(server));
exception.InnerException!.Message.ShouldBe("An error (Code:40003 subCode:isv.not-online-app) occurred while retrieving user information.");
}

[Theory]
[InlineData(ClaimTypes.NameIdentifier, "open-id")]
[InlineData("urn:alipay:avatar", "http://tfsimg.alipay.com/images/partner/T1uIxXXbpXXXXXXXX")]
[InlineData("urn:alipay:province", "my-province")]
[InlineData("urn:alipay:city", "my-city")]
[InlineData("urn:alipay:nick_name", "my-nickname")]
[InlineData("urn:alipay:gender", "M")]
public async Task Can_Sign_In_Using_Alipay_With_Use_Open_Id(string claimType, string claimValue)
{
// Arrange
static void ConfigureServices(IServiceCollection services)
{
services.PostConfigureAll<AlipayAuthenticationOptions>((options) => options.ClientId = "open-id");
}

using var server = CreateTestServer(ConfigureServices);

// Act
var claims = await AuthenticateUserAsync(server);

// Assert
AssertClaim(claims, claimType, claimValue);
}

private sealed class FixedClock : TimeProvider
{
public override DateTimeOffset GetUtcNow() => new(2019, 12, 14, 22, 22, 22, TimeSpan.Zero);
Expand Down
56 changes: 56 additions & 0 deletions test/AspNet.Security.OAuth.Providers.Tests/Alipay/bundle.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,62 @@
},
"sign": "sign"
}
},
{
"uri": "https://openapi.alipay.com/gateway.do?app_id=error-id&charset=utf-8&code=a6ed8e7f-471f-44f1-903b-65946475f351&format=JSON&grant_type=authorization_code&method=alipay.system.oauth.token&sign=N3%2B7cd6Sln12%2BgfKMFUcsqMQbZIbgG%2Br8Bbf5YImFQ%2B3IkFzSjwyBFuFNH3nshwZpGgyw5CQhar1PZapdJyhUwMwwO3gcC1os6vRe7kFGU84mbje3QnlU%2FS98QjFjViZ8fHLlCb1uFd2oCngkDmKaTTma9eLkiVmlu8eQ3tiU0k4ADG09F2y5YmP6vOg8RJxfsIoeImvnLGd7c5gUqU70ub2ekjR0FrWnjNs3wO8leT3QA1FG9bGqnnV3lB2MkiyZssENN0XvC4is8gG4zFdHRW%2BzIL6CA0Vz2CG0uD1g7f%2BUXQzvNJ26p97U%2F1bHWDw9XkTCbhGToGBNkacKy3xkg%3D%3D&sign_type=RSA2&timestamp=2019-12-14%2022%3A22%3A22&version=1.0",
"contentFormat": "json",
"contentJson": {
"alipay_system_oauth_token_response": {
"open_id": "open-id",
"access_token": "secret-access-token",
"expires_in": "300",
"refresh_token": "secret-refresh-token",
"re_expires_in": "300"
},
"sign": "sign"
}
},
{
"uri": "https://openapi.alipay.com/gateway.do?app_id=error-id&auth_token=secret-access-token&charset=utf-8&format=JSON&method=alipay.user.info.share&sign=4p9fbAnCCa%2FUKDkJH2OZcPhHXvDEwy17G9iZjRM%2BxpPc2%2FUw5it8BuBiU57SIkAwFBJjnC0RXGOlur94jO4b9rMyQWNsNXKjIHe3nHPx6fMFEgqSe6SUohuo%2BvvvdFyK1TkvstLgAJXyfAlMLg1SxLUZDLo%2FPga0Emxy2UXjCyTZBgIc%2BzZEBwly5s4NW0Jln3ZFYlpG4yFiuSBFzTCJ4bypVkQ59yANl3kZGnoXBkddieDLA4DGLUN6PacsdGNBFgIA%2BwU9v95gL80SdcHON7cxDDU33Lev0k%2Fu3%2F7EBBN%2BqaCmQS5wL0Huls3n6JAhZvL%2FFEWTa2gvedcm1AtE6w%3D%3D&sign_type=RSA2&timestamp=2019-12-14%2022%3A22%3A22&version=1.0",
"contentFormat": "json",
"contentJson": {
"alipay_user_info_share_response": {
"code": "40003",
"sub_code": "isv.not-online-app"
},
"sign": "sign"
}
},
{
"uri": "https://openapi.alipay.com/gateway.do?app_id=open-id&charset=utf-8&code=a6ed8e7f-471f-44f1-903b-65946475f351&format=JSON&grant_type=authorization_code&method=alipay.system.oauth.token&sign=tvlLQ%2Brp1tu5z8VFP9YpOZyhyAjAhVDz1s8eJ0x78Z0FWSFw1OPBxqramdbz2ZCgKJTbQ3Ajd1eJLlNTp7XzVjpUCLGYpUaUs23GMhk%2BYlMNAYxVvTvVLq7ceKLygpVt4dS5rdUI0zesscsoAB3YMPNZ2yEj%2BCVMvsHIS0VFgt0wmc8HN8Cc7XWTdIeN3LycBecH8rMGs5iakKzeGMHbUh5KKayEPEZqQNZTzVhTNmuf%2BNsLcnfIVPDBdLDVA8FQEm6mRQvNlZd2w8gfWAQk6chf2j%2FpyrEaGEvpliZzG6GRulcuuJ%2FwhCtLdm5KkzN75dxTpTvKdbcWCHbDa2iTjQ%3D%3D&sign_type=RSA2&timestamp=2019-12-14%2022%3A22%3A22&version=1.0",
"contentFormat": "json",
"contentJson": {
"alipay_system_oauth_token_response": {
"open_id": "open-id",
"access_token": "secret-access-token",
"expires_in": "300",
"refresh_token": "secret-refresh-token",
"re_expires_in": "300"
},
"sign": "sign"
}
},
{
"uri": "https://openapi.alipay.com/gateway.do?app_id=open-id&auth_token=secret-access-token&charset=utf-8&format=JSON&method=alipay.user.info.share&sign=uYIsXQsOZnpqWmy4Roa48LHX6D4g%2BDhz%2BdtQfrFTus2hjDiAOxOZFooUy%2Fw6sXHkPYO6vM5JpmNRdXDdontk%2BNP8szwyMHaGEX8%2FKWDzLGErMkJs1gneepnaVPeoTd57fxS6fY58py%2FsObG0zcxGp61wzI3D4fdN6c2AoFy0mOoqeMZixynHjXQFkPtj68pBcfZNlxujDhYkHtWdjrdR%2BPhRT%2FAazABa%2B%2BHLfM6YMHyd3Ryo%2BvSh7Xr%2BANU9n%2F2Ayw%2FvSxjGdd6UnYJ%2FIgi5XTWoAMvmKB%2BBpiS6cnP96HG27mgU2LGnrDAIYvQORLAOutezDeVHB7xHSGvtSobEmg%3D%3D&sign_type=RSA2&timestamp=2019-12-14 22%3A22%3A22&version=1.0",
"contentFormat": "json",
"contentJson": {
"alipay_user_info_share_response": {
"code": "10000",
"msg": "Success",
"open_id": "open-id",
"avatar": "http://tfsimg.alipay.com/images/partner/T1uIxXXbpXXXXXXXX",
"province": "my-province",
"city": "my-city",
"nick_name": "my-nickname",
"gender": "M"
},
"sign": "sign"
}
}
]
}