diff --git a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs index fab5cf71b..b33da5630 100644 --- a/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs +++ b/src/AspNet.Security.OAuth.Alipay/AlipayAuthenticationHandler.cs @@ -81,9 +81,9 @@ protected override async Task 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()); @@ -126,15 +126,16 @@ protected override async Task 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); @@ -153,17 +154,28 @@ protected override async Task CreateTicketAsync( /// /// Main part of json document from response /// Returned code from server + /// Returned sub_code from server /// See https://opendocs.alipay.com/open/common/105806 for details. /// True if succeed, otherwise false. - 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"; } @@ -200,6 +212,22 @@ private string GetRSA2Signature([NotNull] SortedDictionary sort return Convert.ToBase64String(encryptedBytes); } + /// + /// Get user identifier from response. + /// + /// Main part of json document from response + /// See https://opendocs.alipay.com/common/0ai2i6?pathHash=cba76ebf for details. + /// UserId or OpenId + private static string GetUserIdentifier(JsonElement element) + { + if (element.TryGetProperty("user_id", out JsonElement userIdElement)) + { + return userIdElement.GetString()!; + } + + return element.GetString("open_id")!; + } + /// protected override string BuildChallengeUrl([NotNull] AuthenticationProperties properties, [NotNull] string redirectUri) { diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Alipay/AlipayTests.cs b/test/AspNet.Security.OAuth.Providers.Tests/Alipay/AlipayTests.cs index d0c752bde..a07d0541c 100644 --- a/test/AspNet.Security.OAuth.Providers.Tests/Alipay/AlipayTests.cs +++ b/test/AspNet.Security.OAuth.Providers.Tests/Alipay/AlipayTests.cs @@ -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((options) => options.ClientId = "error-id"); + } + + using var server = CreateTestServer(ConfigureServices); + + // Act + var exception = await Assert.ThrowsAsync(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((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); diff --git a/test/AspNet.Security.OAuth.Providers.Tests/Alipay/bundle.json b/test/AspNet.Security.OAuth.Providers.Tests/Alipay/bundle.json index 75c9b2bfd..03f34b600 100644 --- a/test/AspNet.Security.OAuth.Providers.Tests/Alipay/bundle.json +++ b/test/AspNet.Security.OAuth.Providers.Tests/Alipay/bundle.json @@ -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×tamp=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×tamp=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×tamp=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×tamp=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" + } } ] }