Skip to content

Commit

Permalink
Add .NET analyzer authgear#44
Browse files Browse the repository at this point in the history
  • Loading branch information
roxk committed Jun 1, 2022
1 parent a810898 commit 5ba5aec
Show file tree
Hide file tree
Showing 30 changed files with 162 additions and 124 deletions.
40 changes: 39 additions & 1 deletion Authgear.Xamarin/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,42 @@ dotnet_separate_import_directive_groups = false
csharp_new_line_before_open_brace = all
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_finally = true

# CA1032: Implement standard exception constructors
dotnet_diagnostic.CA1032.severity = silent

# CA1031: Do not catch general exception types
dotnet_diagnostic.CA1031.severity = silent

# CA1307: Specify StringComparison for clarity
# Some methods are not available in netstandard
dotnet_diagnostic.CA1307.severity = silent

# CA1056: URI-like properties should not be strings
dotnet_diagnostic.CA1056.severity = silent

# CA2227: Collection properties should be read only
dotnet_diagnostic.CA2227.severity = silent

# CA1003: Use generic event handler instances
dotnet_diagnostic.CA1003.severity = silent

# CA1062: Validate arguments of public methods
# Not needed with NRT
dotnet_diagnostic.CA1062.severity = silent

# CA2208: Instantiate argument exceptions correctly
dotnet_diagnostic.CA2208.severity = silent

# CA2208: Type names should not match namespaces
dotnet_diagnostic.CA1724.severity = silent

# CA1054: URI-like parameters should not be strings
dotnet_diagnostic.CA1054.severity = silent

# CA2237: Mark ISerializable types with SerializableAttribute
dotnet_diagnostic.CA2237.severity = silent

# CA1014: Mark assemblies with CLSCompliantAttribute
dotnet_diagnostic.CA1014.severity = silent
3 changes: 2 additions & 1 deletion Authgear.Xamarin/AnonymousUserIosException.ios.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Globalization;
using Foundation;
using Security;

Expand All @@ -13,7 +14,7 @@ public AnonymousUserIosException(NSError error) : base("")
Error = error;
}

public AnonymousUserIosException(SecStatusCode code) : this(new NSError(NSError.OsStatusErrorDomain, Convert.ToInt32(code)))
public AnonymousUserIosException(SecStatusCode code) : this(new NSError(NSError.OsStatusErrorDomain, Convert.ToInt32(code, CultureInfo.InvariantCulture)))
{

}
Expand Down
2 changes: 1 addition & 1 deletion Authgear.Xamarin/AnonymousUserNotFoundException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Authgear.Xamarin
{
internal class AnonymousUserNotFoundException : Exception
public class AnonymousUserNotFoundException : Exception
{
public AnonymousUserNotFoundException() : base() { }
}
Expand Down
5 changes: 3 additions & 2 deletions Authgear.Xamarin/AuthenticateOptions.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using Authgear.Xamarin.Oauth;

Expand All @@ -9,9 +10,9 @@ public class AuthenticateOptions
{
public string RedirectUri { get; set; }
public string? State { get; set; }
public List<PromptOption>? PromptOptions { get; set; }
public ReadOnlyCollection<PromptOption>? PromptOptions { get; set; }
public string? LoginHint { get; set; }
public List<string>? UiLocales { get; set; }
public ReadOnlyCollection<string>? UiLocales { get; set; }
public ColorScheme? ColorScheme { get; set; }
public AuthenticatePage? Page { get; set; }

Expand Down
3 changes: 2 additions & 1 deletion Authgear.Xamarin/Authgear.Xamarin.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageProjectUrl>https://github.com/authgear/authgear-sdk-xamarin</PackageProjectUrl>
<!-- TODO: Can we use 9.0 for init only setters? -->
<LangVersion>8.0</LangVersion>
<Nullable>enable</Nullable>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="System.Text.Json" Version="6.0.3" />
<PackageReference Include="Xamarin.Essentials" Version="1.7.2" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="6.0.0" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
Expand Down
63 changes: 33 additions & 30 deletions Authgear.Xamarin/AuthgearSdk.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
Expand Down Expand Up @@ -63,9 +64,9 @@ public SessionState SessionState
private readonly IBiometric biometric;
private readonly IWebView webView;
private readonly string name;
private bool isInitialized = false;
private string? refreshToken = null;
private Task? refreshAccessTokenTask = null;
private bool isInitialized;
private string? refreshToken;
private Task? refreshAccessTokenTask;
public event EventHandler<SessionStateChangeReason> SessionStateChange;
private bool ShouldSuppressIDPSessionCookie
{
Expand All @@ -83,6 +84,8 @@ public bool CanReauthenticate
}
}
private readonly object tokenStateLock = new object();


// These fields are configured in the platform's respective constructors
// For brevity, they are not passed into this contructor. If it's proven that bugs are caused by some platform missing initializing
// constructors, change the constructor to accept all those dependencies (think of it as each platform's constructor injecting
Expand Down Expand Up @@ -144,18 +147,18 @@ private void UpdateSessionState(SessionState state, SessionStateChangeReason rea
public async Task<UserInfo> AuthenticateAnonymouslyAsync()
{
EnsureIsInitialized();
var challengeResponse = await oauthRepo.OauthChallengeAsync("anonymous_request");
var challengeResponse = await oauthRepo.OauthChallengeAsync("anonymous_request").ConfigureAwait(false);
var challenge = challengeResponse.Token!;
var keyId = await containerStorage.GetAnonymousKeyIdAsync(name);
var keyId = await containerStorage.GetAnonymousKeyIdAsync(name).ConfigureAwait(false);
var deviceInfo = PlatformGetDeviceInfo();
var jwtResult = await keyRepo.GetOrCreateAnonymousJwtAsync(keyId, challenge, deviceInfo).ConfigureAwait(false);
keyId = jwtResult.KeyId;
var jwt = jwtResult.Jwt;
var tokenResponse = await oauthRepo.OidcTokenRequestAsync(new OidcTokenRequest(GrantType.Anonymous, ClientId, GetDeviceInfoString(deviceInfo))
{
Jwt = jwt
});
var userInfo = await oauthRepo.OidcUserInfoRequestAsync(tokenResponse.AccessToken!);
}).ConfigureAwait(false);
var userInfo = await oauthRepo.OidcUserInfoRequestAsync(tokenResponse.AccessToken!).ConfigureAwait(false);
SaveToken(tokenResponse, SessionStateChangeReason.Authenciated);
await DisableBiometricAsync().ConfigureAwait(false);
containerStorage.SetAnonymousKeyId(name, keyId);
Expand All @@ -175,10 +178,10 @@ public async Task<UserInfo> PromoteAnonymousUserAsync(PromoteOptions options)
throw new ArgumentNullException(nameof(options.RedirectUri));
}
EnsureIsInitialized();
var keyId = (await containerStorage.GetAnonymousKeyIdAsync(name)) ?? throw new AnonymousUserNotFoundException();
var challengeResponse = await oauthRepo.OauthChallengeAsync("anonymous_request");
var keyId = (await containerStorage.GetAnonymousKeyIdAsync(name).ConfigureAwait(false)) ?? throw new AnonymousUserNotFoundException();
var challengeResponse = await oauthRepo.OauthChallengeAsync("anonymous_request").ConfigureAwait(false);
var challenge = challengeResponse.Token!;
var jwt = await keyRepo.PromoteAnonymousUserAsync(keyId, challenge, PlatformGetDeviceInfo());
var jwt = await keyRepo.PromoteAnonymousUserAsync(keyId, challenge, PlatformGetDeviceInfo()).ConfigureAwait(false);
var jwtValue = WebUtility.UrlEncode(jwt);
var loginHint = $"https://authgear.com/login_hint?type=anonymous&jwt={jwtValue}";
var codeVerifier = new CodeVerifier(new RNGCryptoServiceProvider());
Expand Down Expand Up @@ -218,7 +221,7 @@ public async Task<UserInfo> ReauthenticateAsync(ReauthenticateOptions options, B
EnsureIsInitialized();
if (await GetIsBiometricEnabledAsync().ConfigureAwait(false) && biometricOptions != null)
{
return await AuthenticateBiometricAsync(biometricOptions);
return await AuthenticateBiometricAsync(biometricOptions).ConfigureAwait(false);
}
if (!CanReauthenticate)
{
Expand Down Expand Up @@ -257,8 +260,8 @@ public async Task LogoutAsync(bool? force = null)
public async Task OpenUrlAsync(string path, SettingsOptions? options = null)
{
EnsureIsInitialized();
var refreshToken = await tokenStorage.GetRefreshTokenAsync(name) ?? "";
var appSessionTokenResponse = await oauthRepo.OauthAppSessionTokenAsync(refreshToken);
var refreshToken = await tokenStorage.GetRefreshTokenAsync(name).ConfigureAwait(false) ?? "";
var appSessionTokenResponse = await oauthRepo.OauthAppSessionTokenAsync(refreshToken).ConfigureAwait(false);
var token = appSessionTokenResponse.AppSessionToken;

var query = new Dictionary<string, string>();
Expand All @@ -280,7 +283,7 @@ public async Task OpenUrlAsync(string path, SettingsOptions? options = null)
builder.Query = query.ToQueryParameter();
var url = builder.Uri;

var loginHint = string.Format(LoginHintFormat, WebUtility.UrlEncode(token));
var loginHint = string.Format(CultureInfo.InvariantCulture, LoginHintFormat, WebUtility.UrlEncode(token));
if (options == null) { options = new SettingsOptions(); }
var request = options.ToRequest(url.ToString(), loginHint, ShouldSuppressIDPSessionCookie);
var authorizeUrl = await GetAuthorizeEndpointAsync(request, null).ConfigureAwait(false);
Expand Down Expand Up @@ -399,7 +402,7 @@ private async Task<string> OpenAuthorizeUrlAsync(string redirectUrl, string auth
return builder.ToString();
}

private async Task<(UserInfo userInfo, OidcTokenResponse tokenResponse, string state)> ParseDeepLinkAndGetUserAsync(string deepLink, string codeVerifier)
private async Task<(UserInfo userInfo, OidcTokenResponse tokenResponse)> ParseDeepLinkAndGetUserAsync(string deepLink, string codeVerifier)
{
var uri = new Uri(deepLink);
var path = uri.LocalPath == "/" ? "" : uri.LocalPath;
Expand All @@ -423,22 +426,22 @@ private async Task<(UserInfo userInfo, OidcTokenResponse tokenResponse, string s
Code = code,
RedirectUri = redirectUri,
CodeVerifier = codeVerifier ?? "",
});
var userInfo = await oauthRepo.OidcUserInfoRequestAsync(tokenResponse.AccessToken!);
return (userInfo, tokenResponse, state);
}).ConfigureAwait(false);
var userInfo = await oauthRepo.OidcUserInfoRequestAsync(tokenResponse.AccessToken!).ConfigureAwait(false);
return (userInfo, tokenResponse);
}

private async Task<UserInfo> FinishAuthenticationAsync(string deepLink, string codeVerifier)
{
var (userInfo, tokenResponse, _) = await ParseDeepLinkAndGetUserAsync(deepLink, codeVerifier);
var (userInfo, tokenResponse) = await ParseDeepLinkAndGetUserAsync(deepLink, codeVerifier).ConfigureAwait(false);
SaveToken(tokenResponse, SessionStateChangeReason.Authenciated);
await DisableBiometricAsync();
await DisableBiometricAsync().ConfigureAwait(false);
return userInfo;
}

private async Task<UserInfo> FinishReauthenticationAsync(string deepLink, string codeVerifier)
{
var (userInfo, tokenResponse, _) = await ParseDeepLinkAndGetUserAsync(deepLink, codeVerifier);
var (userInfo, tokenResponse) = await ParseDeepLinkAndGetUserAsync(deepLink, codeVerifier).ConfigureAwait(false);
if (tokenResponse.IdToken != null)
{
IdTokenHint = tokenResponse.IdToken;
Expand Down Expand Up @@ -482,10 +485,10 @@ public async Task EnableBiometricAsync(BiometricOptions options)
EnsureIsInitialized();
await RefreshAccessTokenIfNeededAsync().ConfigureAwait(false);
var accessToken = AccessToken ?? throw new UnauthenticatedUserException();
var challengeResponse = await oauthRepo.OauthChallengeAsync("biometric_request");
var challengeResponse = await oauthRepo.OauthChallengeAsync("biometric_request").ConfigureAwait(false);
var challenge = challengeResponse.Token!;
var result = await biometric.EnableBiometricAsync(options, challenge, PlatformGetDeviceInfo());
await oauthRepo.BiometricSetupRequestAsync(accessToken, ClientId, result.Jwt);
var result = await biometric.EnableBiometricAsync(options, challenge, PlatformGetDeviceInfo()).ConfigureAwait(false);
await oauthRepo.BiometricSetupRequestAsync(accessToken, ClientId, result.Jwt).ConfigureAwait(false);
containerStorage.SetBiometricKeyId(name, result.Kid);
}

Expand All @@ -498,8 +501,8 @@ public async Task EnableBiometricAsync(BiometricOptions options)
public async Task<UserInfo> AuthenticateBiometricAsync(BiometricOptions options)
{
EnsureIsInitialized();
var kid = await containerStorage.GetBiometricKeyIdAsync(name) ?? throw new BiometricPrivateKeyNotFoundException();
var challengeResponse = await oauthRepo.OauthChallengeAsync("biometric_request");
var kid = await containerStorage.GetBiometricKeyIdAsync(name).ConfigureAwait(false) ?? throw new BiometricPrivateKeyNotFoundException();
var challengeResponse = await oauthRepo.OauthChallengeAsync("biometric_request").ConfigureAwait(false);
var challenge = challengeResponse.Token!;
try
{
Expand All @@ -510,8 +513,8 @@ public async Task<UserInfo> AuthenticateBiometricAsync(BiometricOptions options)
var tokenResponse = await oauthRepo.OidcTokenRequestAsync(new OidcTokenRequest(GrantType.Biometric, ClientId, GetDeviceInfoString())
{
Jwt = jwt
});
var userInfo = await oauthRepo.OidcUserInfoRequestAsync(tokenResponse.AccessToken!);
}).ConfigureAwait(false);
var userInfo = await oauthRepo.OidcUserInfoRequestAsync(tokenResponse.AccessToken!).ConfigureAwait(false);
SaveToken(tokenResponse, SessionStateChangeReason.Authenciated);
return userInfo;
}
Expand All @@ -538,10 +541,10 @@ public async Task<UserInfo> AuthenticateBiometricAsync(BiometricOptions options)

private string GetDeviceInfoString()
{
return ConvertExtensions.ToBase64UrlSafeString(JsonSerializer.Serialize(PlatformGetDeviceInfo()), Encoding.UTF8);
return GetDeviceInfoString(PlatformGetDeviceInfo());
}

private string GetDeviceInfoString(DeviceInfoRoot deviceInfo)
private static string GetDeviceInfoString(DeviceInfoRoot deviceInfo)
{
return ConvertExtensions.ToBase64UrlSafeString(JsonSerializer.Serialize(deviceInfo), Encoding.UTF8);
}
Expand Down
3 changes: 3 additions & 0 deletions Authgear.Xamarin/AuthgearSdk.ios.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ public AuthgearSdk(UIApplication app, AuthgearOptions options) : this(options)
keyRepo = new KeyRepo();
webView = new WebView();
}
// Other platform's implementation is not static
#pragma warning disable CA1822 // Mark members as static
private DeviceInfoRoot PlatformGetDeviceInfo()
#pragma warning restore CA1822 // Mark members as static
{
return new DeviceInfoRoot
{
Expand Down
3 changes: 2 additions & 1 deletion Authgear.Xamarin/BiometricIosException.ios.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using Foundation;
using Security;
Expand All @@ -15,7 +16,7 @@ public BiometricIosException(NSError error) : base("")
Error = error;
}

public BiometricIosException(SecStatusCode code) : this(new NSError(NSError.OsStatusErrorDomain, Convert.ToInt32(code)))
public BiometricIosException(SecStatusCode code) : this(new NSError(NSError.OsStatusErrorDomain, Convert.ToInt32(code, CultureInfo.InvariantCulture)))
{

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace Authgear.Xamarin
{
internal class BiometricPromptAuthenticationException : Exception
public class BiometricPromptAuthenticationException : Exception
{
public int ErrorCode { get; private set; }
public BiometricPromptAuthenticationException(string message) : base(message) { }
Expand Down
9 changes: 5 additions & 4 deletions Authgear.Xamarin/CodeVerifier.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
Expand All @@ -18,20 +19,20 @@ public CodeVerifier(RandomNumberGenerator generator)
using (var provider = generator)
{
provider.GetBytes(bytes);
Verifier = string.Join("", bytes.Select(x => x.ToString("x2")));
Verifier = string.Join("", bytes.Select(x => x.ToString("x2", CultureInfo.InvariantCulture)));
}
Challenge = ComputeCodeChallenge(Verifier);
}

private string ComputeCodeChallenge(string verifier)
private static string ComputeCodeChallenge(string verifier)
{
var hash = Sha256(verifier);
return ConvertExtensions.ToBase64UrlSafeString(hash);
}

private byte[] Sha256(string input)
private static byte[] Sha256(string input)
{
var sha256 = SHA256.Create();
using var sha256 = SHA256.Create();
return sha256.ComputeHash(Encoding.UTF8.GetBytes(input));
}
}
Expand Down
Loading

0 comments on commit 5ba5aec

Please sign in to comment.