Skip to content

Commit d9bad85

Browse files
Merge pull request #7 from mdellison90-stack/copilot/refactor-duplicated-code
2 parents 60b9a82 + 2fb5fe1 commit d9bad85

File tree

12 files changed

+179
-139
lines changed

12 files changed

+179
-139
lines changed

src/shared/Atlassian.Bitbucket.Tests/BitbucketTokenEndpointResponseJsonTest.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Text.Json;
3+
using GitCredentialManager;
34
using Xunit;
45

56
namespace Atlassian.Bitbucket.Tests
@@ -17,11 +18,7 @@ public void BitbucketTokenEndpointResponseJson_Deserialize_Uses_Scopes()
1718

1819
var json = $"{{\"access_token\": \"{accessToken}\", \"token_type\": \"{tokenType}\", \"expires_in\": {expiresIn}, \"scopes\": \"{scopesString}\", \"scope\": \"{scopeString}\"}}";
1920

20-
var result = JsonSerializer.Deserialize<BitbucketTokenEndpointResponseJson>(json,
21-
new JsonSerializerOptions
22-
{
23-
PropertyNameCaseInsensitive = true
24-
});
21+
var result = JsonSerializer.Deserialize<BitbucketTokenEndpointResponseJson>(json, JsonHelper.CaseInsensitiveOptions);
2522

2623
Assert.Equal(accessToken, result.AccessToken);
2724
Assert.Equal(tokenType, result.TokenType);

src/shared/Atlassian.Bitbucket.Tests/Cloud/UserInfoTest.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Text.Json.Serialization;
44
using System.Threading.Tasks;
55
using Atlassian.Bitbucket.Cloud;
6+
using GitCredentialManager;
67
using Xunit;
78

89
namespace Atlassian.Bitbucket.Tests.Cloud
@@ -29,10 +30,7 @@ public void Deserialize_UserInfo()
2930

3031
var json = $"{{\"uuid\": \"{uuid}\", \"has_2fa_enabled\": null, \"username\": \"{userName}\", \"account_id\": \"{accountId}\"}}";
3132

32-
var result = JsonSerializer.Deserialize<UserInfo>(json, new JsonSerializerOptions()
33-
{
34-
PropertyNameCaseInsensitive = true,
35-
});
33+
var result = JsonSerializer.Deserialize<UserInfo>(json, JsonHelper.CaseInsensitiveOptions);
3634

3735
Assert.Equal(userName, result.UserName);
3836
}

src/shared/Atlassian.Bitbucket/Cloud/BitbucketOAuth2Client.cs

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,44 +23,29 @@ public BitbucketOAuth2Client(HttpClient httpClient, ISettings settings, ITrace2
2323

2424
private static string GetClientId(ISettings settings)
2525
{
26-
// Check for developer override value
27-
if (settings.TryGetSetting(
26+
return settings.GetOAuthConfigValue(
2827
CloudConstants.EnvironmentVariables.OAuthClientId,
29-
Constants.GitConfiguration.Credential.SectionName, CloudConstants.GitConfiguration.Credential.OAuthClientId,
30-
out string clientId))
31-
{
32-
return clientId;
33-
}
34-
35-
return CloudConstants.OAuth2ClientId;
28+
Constants.GitConfiguration.Credential.SectionName,
29+
CloudConstants.GitConfiguration.Credential.OAuthClientId,
30+
CloudConstants.OAuth2ClientId);
3631
}
3732

3833
private static Uri GetRedirectUri(ISettings settings)
3934
{
40-
// Check for developer override value
41-
if (settings.TryGetSetting(
35+
return settings.GetOAuthConfigUri(
4236
CloudConstants.EnvironmentVariables.OAuthRedirectUri,
43-
Constants.GitConfiguration.Credential.SectionName, CloudConstants.GitConfiguration.Credential.OAuthRedirectUri,
44-
out string redirectUriStr) && Uri.TryCreate(redirectUriStr, UriKind.Absolute, out Uri redirectUri))
45-
{
46-
return redirectUri;
47-
}
48-
49-
return CloudConstants.OAuth2RedirectUri;
37+
Constants.GitConfiguration.Credential.SectionName,
38+
CloudConstants.GitConfiguration.Credential.OAuthRedirectUri,
39+
CloudConstants.OAuth2RedirectUri);
5040
}
5141

5242
private static string GetClientSecret(ISettings settings)
5343
{
54-
// Check for developer override value
55-
if (settings.TryGetSetting(
44+
return settings.GetOAuthConfigValue(
5645
CloudConstants.EnvironmentVariables.OAuthClientSecret,
57-
Constants.GitConfiguration.Credential.SectionName, CloudConstants.GitConfiguration.Credential.OAuthClientSecret,
58-
out string clientSecret))
59-
{
60-
return clientSecret;
61-
}
62-
63-
return CloudConstants.OAuth2ClientSecret;
46+
Constants.GitConfiguration.Credential.SectionName,
47+
CloudConstants.GitConfiguration.Credential.OAuthClientSecret,
48+
CloudConstants.OAuth2ClientSecret);
6449
}
6550

6651
private static OAuth2ServerEndpoints GetEndpoints()

src/shared/Atlassian.Bitbucket/Cloud/BitbucketRestApi.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,7 @@ public async Task<RestApiResult<IUserInfo>> GetUserInformationAsync(string userN
4343

4444
if (response.IsSuccessStatusCode)
4545
{
46-
var obj = JsonSerializer.Deserialize<UserInfo>(json,
47-
new JsonSerializerOptions
48-
{
49-
PropertyNameCaseInsensitive = true,
50-
});
46+
var obj = JsonSerializer.Deserialize<UserInfo>(json, JsonHelper.CaseInsensitiveOptions);
5147

5248
return new RestApiResult<IUserInfo>(response.StatusCode, obj);
5349
}

src/shared/Atlassian.Bitbucket/DataCenter/BitbucketOAuth2Client.cs

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -26,44 +26,29 @@ public BitbucketOAuth2Client(HttpClient httpClient, ISettings settings, ITrace2
2626

2727
private static string GetClientId(ISettings settings)
2828
{
29-
// Check for developer override value
30-
if (settings.TryGetSetting(
29+
return settings.GetRequiredOAuthConfigValue(
3130
DataCenterConstants.EnvironmentVariables.OAuthClientId,
32-
Constants.GitConfiguration.Credential.SectionName, DataCenterConstants.GitConfiguration.Credential.OAuthClientId,
33-
out string clientId))
34-
{
35-
return clientId;
36-
}
37-
38-
throw new ArgumentException("Bitbucket DC OAuth Client ID must be defined");
31+
Constants.GitConfiguration.Credential.SectionName,
32+
DataCenterConstants.GitConfiguration.Credential.OAuthClientId,
33+
"Bitbucket DC OAuth Client ID must be defined");
3934
}
4035

4136
private static Uri GetRedirectUri(ISettings settings)
4237
{
43-
// Check for developer override value
44-
if (settings.TryGetSetting(
38+
return settings.GetOAuthConfigUri(
4539
DataCenterConstants.EnvironmentVariables.OAuthRedirectUri,
46-
Constants.GitConfiguration.Credential.SectionName, DataCenterConstants.GitConfiguration.Credential.OAuthRedirectUri,
47-
out string redirectUriStr) && Uri.TryCreate(redirectUriStr, UriKind.Absolute, out Uri redirectUri))
48-
{
49-
return redirectUri;
50-
}
51-
52-
return DataCenterConstants.OAuth2RedirectUri;
40+
Constants.GitConfiguration.Credential.SectionName,
41+
DataCenterConstants.GitConfiguration.Credential.OAuthRedirectUri,
42+
DataCenterConstants.OAuth2RedirectUri);
5343
}
5444

5545
private static string GetClientSecret(ISettings settings)
5646
{
57-
// Check for developer override value
58-
if (settings.TryGetSetting(
47+
return settings.GetRequiredOAuthConfigValue(
5948
DataCenterConstants.EnvironmentVariables.OAuthClientSecret,
60-
Constants.GitConfiguration.Credential.SectionName, DataCenterConstants.GitConfiguration.Credential.OAuthClientSecret,
61-
out string clientSecret))
62-
{
63-
return clientSecret;
64-
}
65-
66-
throw new ArgumentException("Bitbucket DC OAuth Client Secret must be defined");
49+
Constants.GitConfiguration.Credential.SectionName,
50+
DataCenterConstants.GitConfiguration.Credential.OAuthClientSecret,
51+
"Bitbucket DC OAuth Client Secret must be defined");
6752
}
6853

6954
private static OAuth2ServerEndpoints GetEndpoints(ISettings settings)

src/shared/Atlassian.Bitbucket/DataCenter/BitbucketRestApi.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,7 @@ public async Task<List<AuthenticationMethod>> GetAuthenticationMethodsAsync()
107107

108108
if (response.IsSuccessStatusCode)
109109
{
110-
var loginOptions = JsonSerializer.Deserialize<LoginOptions>(json,
111-
new JsonSerializerOptions
112-
{
113-
PropertyNameCaseInsensitive = true,
114-
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
115-
});
110+
var loginOptions = JsonSerializer.Deserialize<LoginOptions>(json, JsonHelper.CaseInsensitiveIgnoreNullOptions);
116111

117112
if (loginOptions.Results.Any(r => "LOGIN_FORM".Equals(r.Type)))
118113
{

src/shared/Core.Tests/TokenEndpointResponseJsonTest.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Text.Json;
3+
using GitCredentialManager;
34
using GitCredentialManager.Authentication.OAuth.Json;
45
using Xunit;
56

@@ -17,11 +18,7 @@ public void TokenEndpointResponseJson_Deserialize_Uses_Scope()
1718
var scopeString = "a,b,c";
1819
var json = $"{{\"access_token\": \"{accessToken}\", \"token_type\": \"{tokenType}\", \"expires_in\": {expiresIn}, \"scopes\": \"{scopesString}\", \"scope\": \"{scopeString}\"}}";
1920

20-
var result = JsonSerializer.Deserialize<TokenEndpointResponseJson>(json,
21-
new JsonSerializerOptions
22-
{
23-
PropertyNameCaseInsensitive = true
24-
});
21+
var result = JsonSerializer.Deserialize<TokenEndpointResponseJson>(json, JsonHelper.CaseInsensitiveOptions);
2522

2623
Assert.Equal(accessToken, result.AccessToken);
2724
Assert.Equal(tokenType, result.TokenType);

src/shared/Core/Authentication/OAuth/OAuth2Client.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -321,10 +321,7 @@ public async Task<OAuth2TokenResult> GetTokenByDeviceCodeAsync(OAuth2DeviceCodeR
321321
return result;
322322
}
323323

324-
var error = JsonSerializer.Deserialize<ErrorResponseJson>(json, new JsonSerializerOptions
325-
{
326-
PropertyNameCaseInsensitive = true
327-
});
324+
var error = JsonSerializer.Deserialize<ErrorResponseJson>(json, JsonHelper.CaseInsensitiveOptions);
328325

329326
switch (error.Error)
330327
{
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using System;
2+
3+
namespace GitCredentialManager.Authentication.OAuth
4+
{
5+
/// <summary>
6+
/// Helper class for retrieving OAuth2 configuration settings from environment variables or Git configuration.
7+
/// </summary>
8+
public static class OAuth2SettingsHelper
9+
{
10+
/// <summary>
11+
/// Retrieves an OAuth configuration value from settings, with fallback to a default value.
12+
/// </summary>
13+
/// <param name="settings">The settings instance to query.</param>
14+
/// <param name="environmentVariable">The environment variable name.</param>
15+
/// <param name="configSection">The Git configuration section name.</param>
16+
/// <param name="configProperty">The Git configuration property name.</param>
17+
/// <param name="defaultValue">The default value to return if no setting is found.</param>
18+
/// <returns>The configured value if found, otherwise the default value.</returns>
19+
public static string GetOAuthConfigValue(
20+
this ISettings settings,
21+
string environmentVariable,
22+
string configSection,
23+
string configProperty,
24+
string defaultValue)
25+
{
26+
if (settings.TryGetSetting(environmentVariable, configSection, configProperty, out string value))
27+
{
28+
return value;
29+
}
30+
31+
return defaultValue;
32+
}
33+
34+
/// <summary>
35+
/// Retrieves a required OAuth configuration value from settings, throwing an exception if not found.
36+
/// </summary>
37+
/// <param name="settings">The settings instance to query.</param>
38+
/// <param name="environmentVariable">The environment variable name.</param>
39+
/// <param name="configSection">The Git configuration section name.</param>
40+
/// <param name="configProperty">The Git configuration property name.</param>
41+
/// <param name="errorMessage">The error message to include in the exception if the value is not found.</param>
42+
/// <returns>The configured value.</returns>
43+
/// <exception cref="ArgumentException">Thrown when the required value is not found.</exception>
44+
public static string GetRequiredOAuthConfigValue(
45+
this ISettings settings,
46+
string environmentVariable,
47+
string configSection,
48+
string configProperty,
49+
string errorMessage)
50+
{
51+
if (settings.TryGetSetting(environmentVariable, configSection, configProperty, out string value))
52+
{
53+
return value;
54+
}
55+
56+
throw new ArgumentException(errorMessage);
57+
}
58+
59+
/// <summary>
60+
/// Retrieves an OAuth configuration URI from settings, with fallback to a default value.
61+
/// </summary>
62+
/// <param name="settings">The settings instance to query.</param>
63+
/// <param name="environmentVariable">The environment variable name.</param>
64+
/// <param name="configSection">The Git configuration section name.</param>
65+
/// <param name="configProperty">The Git configuration property name.</param>
66+
/// <param name="defaultValue">The default URI to return if no setting is found.</param>
67+
/// <returns>The configured URI if found and valid, otherwise the default URI.</returns>
68+
public static Uri GetOAuthConfigUri(
69+
this ISettings settings,
70+
string environmentVariable,
71+
string configSection,
72+
string configProperty,
73+
Uri defaultValue)
74+
{
75+
if (settings.TryGetSetting(environmentVariable, configSection, configProperty, out string uriStr) &&
76+
Uri.TryCreate(uriStr, UriKind.Absolute, out Uri uri))
77+
{
78+
return uri;
79+
}
80+
81+
return defaultValue;
82+
}
83+
}
84+
}

src/shared/Core/JsonHelper.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System.Text.Json;
2+
using System.Text.Json.Serialization;
3+
4+
namespace GitCredentialManager
5+
{
6+
/// <summary>
7+
/// Helper class providing common JSON serialization options and utilities.
8+
/// </summary>
9+
/// <remarks>
10+
/// The shared JsonSerializerOptions instances should not be modified after initialization
11+
/// to ensure thread safety across the application.
12+
/// </remarks>
13+
public static class JsonHelper
14+
{
15+
/// <summary>
16+
/// Gets JSON serializer options configured for case-insensitive property names.
17+
/// Do not modify this instance; create a new instance if different options are needed.
18+
/// </summary>
19+
public static JsonSerializerOptions CaseInsensitiveOptions { get; } = new JsonSerializerOptions
20+
{
21+
PropertyNameCaseInsensitive = true
22+
};
23+
24+
/// <summary>
25+
/// Gets JSON serializer options configured for case-insensitive property names
26+
/// and ignoring null values when writing.
27+
/// Do not modify this instance; create a new instance if different options are needed.
28+
/// </summary>
29+
public static JsonSerializerOptions CaseInsensitiveIgnoreNullOptions { get; } = new JsonSerializerOptions
30+
{
31+
PropertyNameCaseInsensitive = true,
32+
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
33+
};
34+
}
35+
}

0 commit comments

Comments
 (0)