Skip to content

Commit d80d94a

Browse files
committed
Deprecate BuildAuthorizationUrl string grant methods
This commit deprecates the `BuildAuthorizationUrl(..., IEnumerable<string>? grants = null)` methods and provided guidance to use the `BuildAuthorizationUrl(AuthorizationUrlOptions)` method instead. The goal is to improve maintainability, as it's easier to add/remove/deprecate properties from a record than it is to modify the parameters of a method without introducing breaking changes. Scope: oauth, deprecation
1 parent d0625d8 commit d80d94a

File tree

4 files changed

+98
-71
lines changed

4 files changed

+98
-71
lines changed

ShopifySharp.Tests/Utilities/ShopifyOauthUtilityTests.cs

Lines changed: 85 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -63,81 +63,18 @@ public void ShopifyOauthUtility_ShouldUseDomainUtilityFromDependencyInjection()
6363

6464
// Act
6565
var utility = container.BuildServiceProvider().GetService<ShopifyOauthUtility>()!;
66-
var act = () => utility.BuildAuthorizationUrl([ "some-scope" ], ShopDomain, ClientId, RedirectUrl, "some-state", [ "some-grant" ]);
66+
var act = () => utility.BuildAuthorizationUrl(new AuthorizationUrlOptions
67+
{
68+
Scopes = ["some-scope"],
69+
ShopDomain = ShopDomain,
70+
ClientId = ClientId,
71+
RedirectUrl = RedirectUrl
72+
});
6773

6874
// Assert
6975
act.Should().Throw<TestException>();
7076
}
7177

72-
[Theory]
73-
[InlineData(null, null)]
74-
[InlineData("some-state", null)]
75-
[InlineData(null, new [] {"per-user"})]
76-
[InlineData("some-state", new [] { "per-user" })]
77-
public void BuildAuthorizationUrl_WhenGivenStringScopes_ReturnsTheUrl(string? state, string[]? grants)
78-
{
79-
// Setup
80-
var scopes = new[] { "some-permission-1", "some-permission-2" };
81-
82-
// Act
83-
var result = _sut.BuildAuthorizationUrl(scopes, ShopDomain, ClientId, RedirectUrl, state, grants);
84-
85-
// Assert
86-
result.Host.Should().Be("example.myshopify.com");
87-
result.Port.Should().Be(443);
88-
result.Scheme.Should().Be(Uri.UriSchemeHttps);
89-
result.Fragment.Should().BeEmpty();
90-
result.AbsolutePath.Should().Be("/admin/oauth/authorize");
91-
result.Query.Should().Contain($"client_id={ClientId}");
92-
result.Query.Should().Contain("scope=" + string.Join(",", scopes));
93-
result.Query.Should().Contain($"redirect_uri={RedirectUrl}");
94-
95-
if (state is not null)
96-
result.Query.Should().Contain($"state={state}");
97-
else
98-
result.Query.Should().NotContain("state=");
99-
100-
if (grants is not null)
101-
result.Query.Should().ContainAll(grants.Select(grant => $"grant_options[]={grant}"));
102-
else
103-
result.Query.Should().NotContain("grant_options[]=");
104-
}
105-
106-
[Theory]
107-
[InlineData(null, null)]
108-
[InlineData("some-state", null)]
109-
[InlineData(null, new [] {"per-user"})]
110-
[InlineData("some-state", new [] { "per-user" })]
111-
public void BuildAuthorizationUrl_WhenGivenEnumScopes_ReturnsTheUrl(string? state, string[]? grants)
112-
{
113-
// Setup
114-
string[] expectedEnumStrings = ["read_customers", "write_customers"];
115-
AuthorizationScope[] scopes = [ AuthorizationScope.ReadCustomers, AuthorizationScope.WriteCustomers ];
116-
117-
// Act
118-
var result = _sut.BuildAuthorizationUrl(scopes, ShopDomain, ClientId, RedirectUrl, state, grants);
119-
120-
// Assert
121-
result.Host.Should().Be("example.myshopify.com");
122-
result.Port.Should().Be(443);
123-
result.Scheme.Should().Be(Uri.UriSchemeHttps);
124-
result.Fragment.Should().BeEmpty();
125-
result.AbsolutePath.Should().Be("/admin/oauth/authorize");
126-
result.Query.Should().Contain($"client_id={ClientId}");
127-
result.Query.Should().Contain("scope=" + string.Join(",", expectedEnumStrings));
128-
result.Query.Should().Contain($"redirect_uri={RedirectUrl}");
129-
130-
if (state is not null)
131-
result.Query.Should().Contain($"state={state}");
132-
else
133-
result.Query.Should().NotContain("state=");
134-
135-
if (grants is not null)
136-
result.Query.Should().ContainAll(grants.Select(grant => $"grant_options[]={grant}"));
137-
else
138-
result.Query.Should().NotContain("grant_options[]=");
139-
}
140-
14178
[Theory]
14279
[InlineData(null, AuthorizationAccessMode.Offline)]
14380
[InlineData("some-state", AuthorizationAccessMode.Offline)]
@@ -225,6 +162,84 @@ public void BuildAuthorizationUrl_WhenAuthorizationUrlOptionsContainsBothGrantsA
225162
.WithMessage("Invalid AuthorizationUrlOptions. Cannot use the obsolete Grants alongside AuthorizationAccessMode.");
226163
}
227164

165+
#region Deprecated BuildAuthorizationUrl methods
166+
167+
[Theory]
168+
[InlineData(null, null)]
169+
[InlineData("some-state", null)]
170+
[InlineData(null, new [] {"per-user"})]
171+
[InlineData("some-state", new [] { "per-user" })]
172+
public void BuildAuthorizationUrl_WhenGivenStringScopes_ReturnsTheUrl(string? state, string[]? grants)
173+
{
174+
// Setup
175+
var scopes = new[] { "some-permission-1", "some-permission-2" };
176+
177+
// Act
178+
#pragma warning disable CS0618 // Type or member is obsolete
179+
var result = _sut.BuildAuthorizationUrl(scopes, ShopDomain, ClientId, RedirectUrl, state, grants);
180+
#pragma warning restore CS0618 // Type or member is obsolete
181+
182+
// Assert
183+
result.Host.Should().Be("example.myshopify.com");
184+
result.Port.Should().Be(443);
185+
result.Scheme.Should().Be(Uri.UriSchemeHttps);
186+
result.Fragment.Should().BeEmpty();
187+
result.AbsolutePath.Should().Be("/admin/oauth/authorize");
188+
result.Query.Should().Contain($"client_id={ClientId}");
189+
result.Query.Should().Contain("scope=" + string.Join(",", scopes));
190+
result.Query.Should().Contain($"redirect_uri={RedirectUrl}");
191+
192+
if (state is not null)
193+
result.Query.Should().Contain($"state={state}");
194+
else
195+
result.Query.Should().NotContain("state=");
196+
197+
if (grants is not null)
198+
result.Query.Should().ContainAll(grants.Select(grant => $"grant_options[]={grant}"));
199+
else
200+
result.Query.Should().NotContain("grant_options[]=");
201+
}
202+
203+
[Theory]
204+
[InlineData(null, null)]
205+
[InlineData("some-state", null)]
206+
[InlineData(null, new [] {"per-user"})]
207+
[InlineData("some-state", new [] { "per-user" })]
208+
public void BuildAuthorizationUrl_WhenGivenEnumScopes_ReturnsTheUrl(string? state, string[]? grants)
209+
{
210+
// Setup
211+
string[] expectedEnumStrings = ["read_customers", "write_customers"];
212+
AuthorizationScope[] scopes = [ AuthorizationScope.ReadCustomers, AuthorizationScope.WriteCustomers ];
213+
214+
// Act
215+
#pragma warning disable CS0618 // Type or member is obsolete
216+
var result = _sut.BuildAuthorizationUrl(scopes, ShopDomain, ClientId, RedirectUrl, state, grants);
217+
#pragma warning restore CS0618 // Type or member is obsolete
218+
219+
// Assert
220+
result.Host.Should().Be("example.myshopify.com");
221+
result.Port.Should().Be(443);
222+
result.Scheme.Should().Be(Uri.UriSchemeHttps);
223+
result.Fragment.Should().BeEmpty();
224+
result.AbsolutePath.Should().Be("/admin/oauth/authorize");
225+
result.Query.Should().Contain($"client_id={ClientId}");
226+
result.Query.Should().Contain("scope=" + string.Join(",", expectedEnumStrings));
227+
result.Query.Should().Contain($"redirect_uri={RedirectUrl}");
228+
229+
if (state is not null)
230+
result.Query.Should().Contain($"state={state}");
231+
else
232+
result.Query.Should().NotContain("state=");
233+
234+
if (grants is not null)
235+
result.Query.Should().ContainAll(grants.Select(grant => $"grant_options[]={grant}"));
236+
else
237+
result.Query.Should().NotContain("grant_options[]=");
238+
}
239+
240+
241+
#endregion
242+
228243
[Fact(DisplayName = "AuthorizeAsync(AuthorizeOptions) should call the base AuthorizeAsync(string, string, string, string) method")]
229244
public async Task AuthorizeAsync_WithAuthorizeOptionsParameters_ShouldCallBaseAuthorizeAsyncMethodAndPassOptionsToMethod()
230245
{

ShopifySharp/Enums/AuthorizationAccessMode.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@ namespace ShopifySharp.Enums;
33

44
public enum AuthorizationAccessMode
55
{
6+
/// <summary>
7+
/// The default, permanent access token mode scoped to the full permissions the shop grants to your app.
8+
/// </summary>
69
Offline = 1,
710
/// <summary>
11+
/// The temporary, online access token mode scoped to a single shop staff user and the permissions they hold on the shop.
812
/// Alternately referred to as "Per-User" in Shopify's documentation.
913
/// </summary>
1014
Online = 2,

ShopifySharp/Utilities/AuthorizationUrlOptions.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public record AuthorizationUrlOptions
4242
[Obsolete("Deprecated, please use " + nameof(Enums.AuthorizationAccessMode) + " instead.")]
4343
public IEnumerable<string>? Grants { get; set; }
4444

45-
/// Sets the access mode. For an "online access token", set to PerUserAccess.
45+
/// Sets the access mode for the access token grant. For an "online access token" (alternately referred to as "per user"
46+
/// in Shopify's documentation), set to <see cref="AuthorizationAccessMode.Online"/>.
4647
public AuthorizationAccessMode AuthorizationAccessMode { get; set; } = AuthorizationAccessMode.Offline;
4748
}

ShopifySharp/Utilities/ShopifyOauthUtility.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ public interface IShopifyOauthUtility
2424
/// <param name="redirectUrl">URL to redirect the user to after integration.</param>
2525
/// <param name="state">An optional, random string value provided by your application which is unique for each authorization request. During the OAuth callback phase, your application should check that this value matches the one you provided to this method.</param>
2626
/// <param name="grants">Requested grant types, which will change the type of access token granted upon OAuth completion.</param>
27+
/// <remarks>
28+
/// Use the <see cref="BuildAuthorizationUrl(AuthorizationUrlOptions)"/> overload instead.
29+
/// </remarks>
30+
[Obsolete("Use " + nameof(BuildAuthorizationUrl) + "(" + nameof(AuthorizationUrlOptions) + ") instead. This method will be removed in a future version of ShopifySharp.")]
2731
Uri BuildAuthorizationUrl(
2832
IEnumerable<AuthorizationScope> scopes,
2933
string shopDomain,
@@ -42,6 +46,7 @@ Uri BuildAuthorizationUrl(
4246
/// <param name="redirectUrl">URL to redirect the user to after integration.</param>
4347
/// <param name="state">An optional, random string value provided by your application which is unique for each authorization request. During the OAuth callback phase, your application should check that this value matches the one you provided to this method.</param>
4448
/// <param name="grants">Requested grant types, which will change the type of access token granted upon OAuth completion.</param>
49+
[Obsolete("Use " + nameof(BuildAuthorizationUrl) + "(" + nameof(AuthorizationUrlOptions) + ") instead. This method will be removed in a future version of ShopifySharp.")]
4550
Uri BuildAuthorizationUrl(
4651
IEnumerable<string> scopes,
4752
string shopDomain,
@@ -140,6 +145,7 @@ JsonSerializerOptions GetJsonSerializerOptions() => InternalServiceResolver.GetS
140145
}
141146

142147
/// <inheritdoc />
148+
[Obsolete("Use " + nameof(BuildAuthorizationUrl) + "(" + nameof(AuthorizationUrlOptions) + ") instead. This method will be removed in a future version of ShopifySharp.")]
143149
public Uri BuildAuthorizationUrl(
144150
IEnumerable<AuthorizationScope> scopes,
145151
string shopDomain,
@@ -160,6 +166,7 @@ public Uri BuildAuthorizationUrl(
160166
});
161167

162168
/// <inheritdoc />
169+
[Obsolete("Use " + nameof(BuildAuthorizationUrl) + "(" + nameof(AuthorizationUrlOptions) + ") instead. This method will be removed in a future version of ShopifySharp.")]
163170
public Uri BuildAuthorizationUrl(
164171
IEnumerable<string> scopes,
165172
string shopDomain,

0 commit comments

Comments
 (0)