Skip to content

Commit 00d2c9e

Browse files
authored
Add QuickBooks provider (#593)
* Add the QuickBooks provider * Change the author of QuickBooks property * Change QuickBooks project property from TargetFramework to TargetFrameworks * Update AspNet.Security.OAuth.Providers.sln * Update AspNet.Security.OAuth.QuickBooks.csproj * Changed the QuickBooks authentication options. Add the test cases for all the mapped claims. Change the email to a fake email address. * Move the version from the csproj to pakcages.props. Clean the QuickBooksAuthenticationHandler. * Removed the unnecessary configuration and codes Changed the QuickBooks provider URL from test to production. Changed the scope implementation as recommended.
1 parent 6759e8d commit 00d2c9e

9 files changed

+356
-1
lines changed

AspNet.Security.OAuth.Providers.sln

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNet.Security.OAuth.Mixcl
257257
EndProject
258258
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNet.Security.OAuth.AdobeIO", "src\AspNet.Security.OAuth.AdobeIO\AspNet.Security.OAuth.AdobeIO.csproj", "{91BB9A49-9C88-4132-96E4-2D6148ACDE77}"
259259
EndProject
260-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNet.Security.OAuth.Zendesk", "src\AspNet.Security.OAuth.Zendesk\AspNet.Security.OAuth.Zendesk.csproj", "{C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}"
260+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNet.Security.OAuth.Zendesk", "src\AspNet.Security.OAuth.Zendesk\AspNet.Security.OAuth.Zendesk.csproj", "{C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}"
261+
EndProject
262+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AspNet.Security.OAuth.QuickBooks", "src\AspNet.Security.OAuth.QuickBooks\AspNet.Security.OAuth.QuickBooks.csproj", "{815DA59A-E884-4BAD-B16C-D0B550B40A8D}"
261263
EndProject
262264
Global
263265
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -585,6 +587,10 @@ Global
585587
{C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Debug|Any CPU.Build.0 = Debug|Any CPU
586588
{C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Release|Any CPU.ActiveCfg = Release|Any CPU
587589
{C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40}.Release|Any CPU.Build.0 = Release|Any CPU
590+
{815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
591+
{815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Debug|Any CPU.Build.0 = Debug|Any CPU
592+
{815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Release|Any CPU.ActiveCfg = Release|Any CPU
593+
{815DA59A-E884-4BAD-B16C-D0B550B40A8D}.Release|Any CPU.Build.0 = Release|Any CPU
588594
EndGlobalSection
589595
GlobalSection(SolutionProperties) = preSolution
590596
HideSolutionNode = FALSE
@@ -676,6 +682,7 @@ Global
676682
{7A2EC21F-D411-4B45-AA3B-70143C1A9E2F} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
677683
{91BB9A49-9C88-4132-96E4-2D6148ACDE77} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
678684
{C90BA18B-E6C4-4E84-AFCC-58A4EE13BA40} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
685+
{815DA59A-E884-4BAD-B16C-D0B550B40A8D} = {C1352FD3-AE8B-43EE-B45B-F6E0B3FBAC6D}
679686
EndGlobalSection
680687
GlobalSection(ExtensibilityGlobals) = postSolution
681688
SolutionGuid = {C7B54DE2-6407-4802-AD9C-CE54BF414C8C}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFrameworks>$(DefaultNetCoreTargetFramework)</TargetFrameworks>
5+
</PropertyGroup>
6+
7+
<PropertyGroup>
8+
<Description>ASP.NET Core security middleware enabling QuickBooks authentication.</Description>
9+
<Authors>RekingShui</Authors>
10+
<PackageTags>quickbooks;aspnetcore;authentication;oauth;security</PackageTags>
11+
</PropertyGroup>
12+
13+
<ItemGroup>
14+
<FrameworkReference Include="Microsoft.AspNetCore.App" />
15+
<PackageReference Include="JetBrains.Annotations" PrivateAssets="All" />
16+
</ItemGroup>
17+
</Project>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
3+
* See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
4+
* for more information concerning the license and the contributors participating to this project.
5+
*/
6+
7+
namespace AspNet.Security.OAuth.QuickBooks
8+
{
9+
/// <summary>
10+
/// Contains constants specific to the <see cref="QuickBooksAuthenticationHandler"/>.
11+
/// </summary>
12+
public static class QuickBooksAuthenticationConstants
13+
{
14+
public static class Claims
15+
{
16+
public const string AccountType = "urn:quickbooks:appenvrionment";
17+
public const string EmailVerified = "urn:quickbooks:email_verified";
18+
}
19+
}
20+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
3+
* See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
4+
* for more information concerning the license and the contributors participating to this project.
5+
*/
6+
7+
using Microsoft.AspNetCore.Authentication;
8+
using Microsoft.AspNetCore.Authentication.OAuth;
9+
10+
namespace AspNet.Security.OAuth.QuickBooks
11+
{
12+
public static class QuickBooksAuthenticationDefaults
13+
{
14+
/// <summary>
15+
/// Default value for <see cref="AuthenticationScheme.Name"/>.
16+
/// </summary>
17+
public const string AuthenticationScheme = "QuickBooks";
18+
19+
/// <summary>
20+
/// Default value for <see cref="AuthenticationScheme.DisplayName"/>.
21+
/// </summary>
22+
public const string DisplayName = "QuickBooks";
23+
24+
/// <summary>
25+
/// Default value for <see cref="AuthenticationSchemeOptions.ClaimsIssuer"/>.
26+
/// </summary>
27+
public const string Issuer = "https://oauth.platform.intuit.com/op/v1";
28+
29+
/// <summary>
30+
/// Default value for <see cref="RemoteAuthenticationOptions.CallbackPath"/>.
31+
/// </summary>
32+
public const string CallbackPath = "/signin-quickbooks";
33+
34+
/// <summary>
35+
/// Default value for <see cref="OAuthOptions.AuthorizationEndpoint"/>.
36+
/// </summary>
37+
public const string AuthorizationEndpoint = "https://appcenter.intuit.com/connect/oauth2";
38+
39+
/// <summary>
40+
/// Default value for <see cref="OAuthOptions.TokenEndpoint"/>.
41+
/// </summary>
42+
public const string TokenEndpoint = "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer";
43+
44+
/// <summary>
45+
/// Default value for <see cref="OAuthOptions.UserInformationEndpoint"/>.
46+
/// </summary>
47+
public const string UserInformationEndpoint = "https://accounts.platform.intuit.com/v1/openid_connect/userinfo";
48+
}
49+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
3+
* See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
4+
* for more information concerning the license and the contributors participating to this project.
5+
*/
6+
7+
using System;
8+
using AspNet.Security.OAuth.QuickBooks;
9+
using JetBrains.Annotations;
10+
using Microsoft.AspNetCore.Authentication;
11+
12+
namespace Microsoft.Extensions.DependencyInjection
13+
{
14+
/// <summary>
15+
/// Extension methods to add QuickBooks authentication capabilities to an HTTP application pipeline.
16+
/// </summary>
17+
public static class QuickBooksAuthenticationExtensions
18+
{
19+
/// <summary>
20+
/// Adds <see cref="QuickBooksAuthenticationHandler"/> to the specified
21+
/// <see cref="AuthenticationBuilder"/>, which enables QuickBooks authentication capabilities.
22+
/// </summary>
23+
/// <param name="builder">The authentication builder.</param>
24+
/// <returns>The <see cref="AuthenticationBuilder"/>.</returns>
25+
public static AuthenticationBuilder AddQuickBooks([NotNull] this AuthenticationBuilder builder)
26+
{
27+
return builder.AddQuickBooks(QuickBooksAuthenticationDefaults.AuthenticationScheme, options => { });
28+
}
29+
30+
/// <summary>
31+
/// Adds <see cref="QuickBooksAuthenticationHandler"/> to the specified
32+
/// <see cref="AuthenticationBuilder"/>, which enables QuickBooks authentication capabilities.
33+
/// </summary>
34+
/// <param name="builder">The authentication builder.</param>
35+
/// <param name="configuration">The delegate used to configure the OpenID 2.0 options.</param>
36+
/// <returns>The <see cref="AuthenticationBuilder"/>.</returns>
37+
public static AuthenticationBuilder AddQuickBooks(
38+
[NotNull] this AuthenticationBuilder builder,
39+
[NotNull] Action<QuickBooksAuthenticationOptions> configuration)
40+
{
41+
return builder.AddQuickBooks(QuickBooksAuthenticationDefaults.AuthenticationScheme, configuration);
42+
}
43+
44+
/// <summary>
45+
/// Adds <see cref="QuickBooksAuthenticationHandler"/> to the specified
46+
/// <see cref="AuthenticationBuilder"/>, which enables QuickBooks authentication capabilities.
47+
/// </summary>
48+
/// <param name="builder">The authentication builder.</param>
49+
/// <param name="scheme">The authentication scheme associated with this instance.</param>
50+
/// <param name="configuration">The delegate used to configure the QuickBooks options.</param>
51+
/// <returns>The <see cref="AuthenticationBuilder"/>.</returns>
52+
public static AuthenticationBuilder AddQuickBooks(
53+
[NotNull] this AuthenticationBuilder builder,
54+
[NotNull] string scheme,
55+
[NotNull] Action<QuickBooksAuthenticationOptions> configuration)
56+
{
57+
return builder.AddQuickBooks(scheme, QuickBooksAuthenticationDefaults.DisplayName, configuration);
58+
}
59+
60+
/// <summary>
61+
/// Adds <see cref="QuickBooksAuthenticationHandler"/> to the specified
62+
/// <see cref="AuthenticationBuilder"/>, which enables QuickBooks authentication capabilities.
63+
/// </summary>
64+
/// <param name="builder">The authentication builder.</param>
65+
/// <param name="scheme">The authentication scheme associated with this instance.</param>
66+
/// <param name="caption">The optional display name associated with this instance.</param>
67+
/// <param name="configuration">The delegate used to configure the QuickBooks options.</param>
68+
/// <returns>The <see cref="AuthenticationBuilder"/>.</returns>
69+
public static AuthenticationBuilder AddQuickBooks(
70+
[NotNull] this AuthenticationBuilder builder,
71+
[NotNull] string scheme,
72+
[CanBeNull] string caption,
73+
[NotNull] Action<QuickBooksAuthenticationOptions> configuration)
74+
{
75+
return builder.AddOAuth<QuickBooksAuthenticationOptions, QuickBooksAuthenticationHandler>(scheme, caption, configuration);
76+
}
77+
}
78+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
3+
* See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
4+
* for more information concerning the license and the contributors participating to this project.
5+
*/
6+
7+
using System.Collections.Generic;
8+
using System.Net.Http;
9+
using System.Net.Http.Headers;
10+
using System.Security.Claims;
11+
using System.Text.Encodings.Web;
12+
using System.Text.Json;
13+
using System.Threading.Tasks;
14+
using JetBrains.Annotations;
15+
using Microsoft.AspNetCore.Authentication;
16+
using Microsoft.AspNetCore.Authentication.OAuth;
17+
using Microsoft.AspNetCore.WebUtilities;
18+
using Microsoft.Extensions.Logging;
19+
using Microsoft.Extensions.Options;
20+
21+
namespace AspNet.Security.OAuth.QuickBooks
22+
{
23+
public class QuickBooksAuthenticationHandler : OAuthHandler<QuickBooksAuthenticationOptions>
24+
{
25+
public QuickBooksAuthenticationHandler(
26+
[NotNull] IOptionsMonitor<QuickBooksAuthenticationOptions> options,
27+
[NotNull] ILoggerFactory logger,
28+
[NotNull] UrlEncoder encoder,
29+
[NotNull] ISystemClock clock)
30+
: base(options, logger, encoder, clock)
31+
{
32+
}
33+
34+
protected override async Task<AuthenticationTicket> CreateTicketAsync(
35+
[NotNull] ClaimsIdentity identity,
36+
[NotNull] AuthenticationProperties properties,
37+
[NotNull] OAuthTokenResponse tokens)
38+
{
39+
string address = QueryHelpers.AddQueryString(Options.UserInformationEndpoint, "client_id", Options.ClientId);
40+
41+
using var request = new HttpRequestMessage(HttpMethod.Get, address);
42+
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
43+
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken);
44+
45+
using var response = await Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, Context.RequestAborted);
46+
if (!response.IsSuccessStatusCode)
47+
{
48+
Logger.LogError("An error occurred while retrieving the user profile: the remote server " +
49+
"returned a {Status} response with the following payload: {Headers} {Body}.",
50+
/* Status: */ response.StatusCode,
51+
/* Headers: */ response.Headers.ToString(),
52+
/* Body: */ await response.Content.ReadAsStringAsync(Context.RequestAborted));
53+
54+
throw new HttpRequestException("An error occurred while retrieving the user profile.");
55+
}
56+
57+
using var payload = JsonDocument.Parse(await response.Content.ReadAsStringAsync(Context.RequestAborted));
58+
59+
var principal = new ClaimsPrincipal(identity);
60+
var context = new OAuthCreatingTicketContext(principal, properties, Context, Scheme, Options, Backchannel, tokens, payload.RootElement);
61+
context.RunClaimActions();
62+
63+
await Options.Events.CreatingTicket(context);
64+
return new AuthenticationTicket(context.Principal!, context.Properties, Scheme.Name);
65+
}
66+
}
67+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
3+
* See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
4+
* for more information concerning the license and the contributors participating to this project.
5+
*/
6+
7+
using System.Security.Claims;
8+
using Microsoft.AspNetCore.Authentication;
9+
using Microsoft.AspNetCore.Authentication.OAuth;
10+
using static AspNet.Security.OAuth.QuickBooks.QuickBooksAuthenticationConstants;
11+
12+
namespace AspNet.Security.OAuth.QuickBooks
13+
{
14+
/// <summary>
15+
/// Defines a set of options used by <see cref="QuickBooksAuthenticationHandler"/>.
16+
/// </summary>
17+
public class QuickBooksAuthenticationOptions : OAuthOptions
18+
{
19+
public QuickBooksAuthenticationOptions()
20+
{
21+
ClaimsIssuer = QuickBooksAuthenticationDefaults.Issuer;
22+
CallbackPath = QuickBooksAuthenticationDefaults.CallbackPath;
23+
24+
AuthorizationEndpoint = QuickBooksAuthenticationDefaults.AuthorizationEndpoint;
25+
TokenEndpoint = QuickBooksAuthenticationDefaults.TokenEndpoint;
26+
UserInformationEndpoint = QuickBooksAuthenticationDefaults.UserInformationEndpoint;
27+
28+
Scope.Add("openid");
29+
Scope.Add("profile");
30+
Scope.Add("phone");
31+
Scope.Add("email");
32+
33+
ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub");
34+
ClaimActions.MapJsonKey(ClaimTypes.MobilePhone, "mobilephone");
35+
ClaimActions.MapJsonKey(ClaimTypes.Email, "email");
36+
ClaimActions.MapJsonKey(Claims.EmailVerified, "emailVerified");
37+
}
38+
}
39+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
3+
* See https://github.com/aspnet-contrib/AspNet.Security.OAuth.Providers
4+
* for more information concerning the license and the contributors participating to this project.
5+
*/
6+
7+
using System.Security.Claims;
8+
using System.Threading.Tasks;
9+
using AspNet.Security.OAuth.QuickBooks;
10+
using Microsoft.AspNetCore.Authentication;
11+
using Microsoft.Extensions.DependencyInjection;
12+
using Xunit;
13+
using Xunit.Abstractions;
14+
using static AspNet.Security.OAuth.QuickBooks.QuickBooksAuthenticationConstants;
15+
16+
namespace AspNet.Security.OAuth.QuickBooksTests
17+
{
18+
public class QuickBooksTests : OAuthTests<QuickBooksAuthenticationOptions>
19+
{
20+
public QuickBooksTests(ITestOutputHelper outputHelper)
21+
{
22+
OutputHelper = outputHelper;
23+
}
24+
25+
public override string DefaultScheme => QuickBooksAuthenticationDefaults.AuthenticationScheme;
26+
27+
protected internal override void RegisterAuthentication(AuthenticationBuilder builder)
28+
{
29+
builder.AddQuickBooks(options =>
30+
{
31+
ConfigureDefaults(builder, options);
32+
});
33+
}
34+
35+
[Theory]
36+
[InlineData(ClaimTypes.NameIdentifier, "2039290222")]
37+
[InlineData(ClaimTypes.MobilePhone, "(314)000-0000")]
38+
[InlineData(ClaimTypes.Email, "[email protected]")]
39+
[InlineData(Claims.EmailVerified, "true")]
40+
public async Task Can_Sign_In_Using_QuickBooks(string claimType, string claimValue)
41+
{
42+
// Arrange
43+
using var server = CreateTestServer();
44+
45+
// Act
46+
var claims = await AuthenticateUserAsync(server);
47+
48+
// Assert
49+
AssertClaim(claims, claimType, claimValue);
50+
}
51+
}
52+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"$schema": "https://raw.githubusercontent.com/justeat/httpclient-interception/master/src/HttpClientInterception/Bundles/http-request-bundle-schema.json",
3+
"items": [
4+
{
5+
"uri": "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer",
6+
"method": "POST",
7+
"contentFormat": "json",
8+
"contentJson": {
9+
"access_token": "secret-access-token",
10+
"token_type": "access",
11+
"refresh_token": "secret-refresh-token",
12+
"expires_in": "300"
13+
}
14+
},
15+
{
16+
"uri": "https://accounts.platform.intuit.com/v1/openid_connect/userinfo?client_id=my-client-id",
17+
"contentFormat": "json",
18+
"contentJson": {
19+
"sub": "2039290222",
20+
"mobilephone": "(314)000-0000",
21+
"email": "[email protected]",
22+
"emailVerified": "true"
23+
}
24+
}
25+
]
26+
}

0 commit comments

Comments
 (0)