Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Mono.Android] NTLM support in AndroidMessageHandler #6999

Merged
merged 39 commits into from
Jul 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
f856a57
Add initial implementation of NT Authentication
simonrozsival May 12, 2022
532b35b
Refactor NTAuthenticationHelper into NTAuthenticationHandler
simonrozsival May 31, 2022
449151e
Update apkdesc files
simonrozsival Jun 2, 2022
2b259af
Add expected assemblies
simonrozsival Jun 2, 2022
6f324fb
Revert "Add expected assemblies"
simonrozsival Jun 8, 2022
e683125
Revert "Update apkdesc files"
simonrozsival Jun 8, 2022
5af6d31
Require explicit opt-in to enable the feature
simonrozsival Jun 9, 2022
e207f2e
Fix switch default value
simonrozsival Jun 10, 2022
26d38df
Update linker tests
simonrozsival Jun 10, 2022
259a7c6
Renaming
simonrozsival Jun 14, 2022
fe3b508
Merge branch 'main' of https://github.com/xamarin/xamarin-android int…
simonrozsival Jun 14, 2022
ce06b06
Make helper fully static + update linker tests
simonrozsival Jun 15, 2022
5496a14
Try fixing failing tests by updating the csproj
simonrozsival Jun 20, 2022
a4fe8a2
Remove unnececssary setup method
simonrozsival Jun 20, 2022
099ccf0
Merge branch 'main' of https://github.com/xamarin/xamarin-android int…
simonrozsival Jun 20, 2022
483a0cd
Revert "Remove unnececssary setup method"
simonrozsival Jun 21, 2022
d1eb07e
Fix tests
simonrozsival Jun 21, 2022
c8b5172
Remove broken linker test
simonrozsival Jun 28, 2022
d224432
Merge branch 'main' of https://github.com/xamarin/xamarin-android int…
simonrozsival Jun 28, 2022
a1efc4a
Merge branch 'main' of https://github.com/xamarin/xamarin-android int…
simonrozsival Jun 29, 2022
9f9af1f
Fix trimming
simonrozsival Jun 29, 2022
7fa4450
Try enabling tests in Release mode
simonrozsival Jun 30, 2022
9f2cf98
Revert "Remove broken linker test"
simonrozsival Jun 30, 2022
a588024
Add rudimentary documentation for the feature flag
simonrozsival Jun 30, 2022
c793520
Remove outdated comment
simonrozsival Jun 30, 2022
6feb2c9
Merge branch 'main' of https://github.com/xamarin/xamarin-android int…
simonrozsival Jul 5, 2022
8d8ee9b
Use the new NegotiateAuthentication API
simonrozsival Jul 5, 2022
d6ecdd7
Address review
simonrozsival Jul 7, 2022
4281f93
Merge branch 'main' of https://github.com/xamarin/xamarin-android int…
simonrozsival Jul 11, 2022
fb90144
Move auth tests to the shared project
simonrozsival Jul 19, 2022
c109b5f
WIP: update linker tests
simonrozsival Jul 19, 2022
d0656c6
Update property comment
simonrozsival Jul 19, 2022
74a9248
Update docs
simonrozsival Jul 20, 2022
9c9aa27
Fall back to class from resource
simonrozsival Jul 20, 2022
c3358f0
Add comment to clarify usage of SetSwitch at runtime
simonrozsival Jul 20, 2022
58f9521
Fix typo
simonrozsival Jul 20, 2022
ab5b60b
Update setting the property + tests
simonrozsival Jul 20, 2022
0affd46
Remove invalid part of linker test
simonrozsival Jul 20, 2022
3def29d
Code cleanup
simonrozsival Jul 25, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Documentation/guides/building-apps/build-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -1328,6 +1328,12 @@ than `aapt`.

Added in Xamarin.Android 8.1.

## AndroidUseNegotiateAuthentication

A boolean property which enables support for NTLM/Negotiate authentication in `AndroidMessageHandler`. The feature is disabled by default.

Support for this property was added in .NET 7 and has no effect in "legacy" Xamarin.Android.

## AndroidUseSharedRuntime

A boolean property that
Expand Down
4 changes: 4 additions & 0 deletions src/Mono.Android/ILLink/ILLink.Substitutions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,9 @@
<method signature="System.Void RegisterPackage(System.String,System.Converter`2&lt;System.String,System.Type&gt;)" body="stub" />
<method signature="System.Void RegisterPackages(System.String[],System.Converter`2&lt;System.String,System.Type&gt;[])" body="stub" />
</type>
<type fullname="Xamarin.Android.Net.AndroidMessageHandler">
<method signature="System.Boolean get_NegotiateAuthenticationIsEnabled()" body="stub" feature="Xamarin.Android.Net.UseNegotiateAuthentication" featurevalue="false" value="false" />
<method signature="System.Boolean get_NegotiateAuthenticationIsEnabled()" body="stub" feature="Xamarin.Android.Net.UseNegotiateAuthentication" featurevalue="true" value="true" />
</type>
</assembly>
</linker>
1 change: 1 addition & 0 deletions src/Mono.Android/Mono.Android.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@
<Compile Include="Xamarin.Android.Net\AuthModuleDigest.cs" />
<Compile Include="Xamarin.Android.Net\IAndroidAuthenticationModule.cs" />
<Compile Include="Xamarin.Android.Net\X509TrustManagerWithValidationCallback.cs" />
<Compile Condition=" '$(TargetFramework)' != 'monoandroid10' " Include="Xamarin.Android.Net\NegotiateAuthenticationHelper.cs" />
<Compile Condition=" '$(TargetFramework)' == 'monoandroid10' " Include="Xamarin.Android.Net\OldAndroidSSLSocketFactory.cs" />
</ItemGroup>

Expand Down
74 changes: 55 additions & 19 deletions src/Mono.Android/Xamarin.Android.Net/AndroidMessageHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ public int MaxAutomaticRedirections
/// </summary>
/// <value>The pre authentication data.</value>
public AuthenticationData? PreAuthenticationData { get; set; }

/// <summary>
/// If the website requires authentication, this property will contain data about each scheme supported
/// by the server after the response. Note that unauthorized request will return a valid response - you
Expand Down Expand Up @@ -234,12 +234,12 @@ public bool RequestNeedsAuthorization {
/// <summary>
/// <para>
/// If the request is to the server protected with a self-signed (or otherwise untrusted) SSL certificate, the request will
/// fail security chain verification unless the application provides either the CA certificate of the entity which issued the
/// fail security chain verification unless the application provides either the CA certificate of the entity which issued the
/// server's certificate or, alternatively, provides the server public key. Whichever the case, the certificate(s) must be stored
/// in this property in order for AndroidMessageHandler to configure the request to accept the server certificate.</para>
/// <para>AndroidMessageHandler uses a custom <see cref="KeyStore"/> and <see cref="TrustManagerFactory"/> to configure the connection.
/// <para>AndroidMessageHandler uses a custom <see cref="KeyStore"/> and <see cref="TrustManagerFactory"/> to configure the connection.
/// If, however, the application requires finer control over the SSL configuration (e.g. it implements its own TrustManager) then
/// it should leave this property empty and instead derive a custom class from AndroidMessageHandler and override, as needed, the
/// it should leave this property empty and instead derive a custom class from AndroidMessageHandler and override, as needed, the
/// <see cref="ConfigureTrustManagerFactory"/>, <see cref="ConfigureKeyManagerFactory"/> and <see cref="ConfigureKeyStore"/> methods
/// instead</para>
/// </summary>
Expand All @@ -264,6 +264,16 @@ public bool RequestNeedsAuthorization {
/// </summary>
public TimeSpan ReadTimeout { get; set; } = TimeSpan.FromHours (24);

#if !MONOANDROID1_0
/// <summary>
/// A feature switch that determines whether the message handler should attempt to authenticate the user
/// using the NTLM/Negotiate authentication method. Enable the feature by adding
/// <c><AndroidUseNegotiateAuthentication>true</AndroidUseNegotiateAuthentication></c> to your project file.
/// </summary>
static bool NegotiateAuthenticationIsEnabled =>
AppContext.TryGetSwitch ("Xamarin.Android.Net.UseNegotiateAuthentication", out bool isEnabled) && isEnabled;
#endif

/// <summary>
/// <para>
/// Specifies the connect timeout
Expand Down Expand Up @@ -331,12 +341,38 @@ string EncodeUrl (Uri url)
/// <returns>Task in which the request is executed</returns>
/// <param name="request">Request provided by <see cref="System.Net.Http.HttpClient"/></param>
/// <param name="cancellationToken">Cancellation token.</param>
protected override async Task <HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken)
protected override Task <HttpResponseMessage> SendAsync (HttpRequestMessage request, CancellationToken cancellationToken)
{
#if !MONOANDROID1_0
if (NegotiateAuthenticationIsEnabled) {
return SendWithNegotiateAuthenticationAsync (request, cancellationToken);
}
#endif

return DoSendAsync (request, cancellationToken);
}

#if !MONOANDROID1_0
async Task <HttpResponseMessage?> SendWithNegotiateAuthenticationAsync (HttpRequestMessage request, CancellationToken cancellationToken)
{
var response = await DoSendAsync (request, cancellationToken).ConfigureAwait (false);

if (RequestNeedsAuthorization && NegotiateAuthenticationHelper.RequestNeedsNegotiateAuthentication (this, request, out var requestedAuth)) {
var authenticatedResponse = await NegotiateAuthenticationHelper.SendWithAuthAsync (this, request, requestedAuth, cancellationToken).ConfigureAwait (false);
if (authenticatedResponse != null)
return authenticatedResponse;
}

return response;
}
#endif

internal async Task <HttpResponseMessage> DoSendAsync (HttpRequestMessage request, CancellationToken cancellationToken)
{
AssertSelf ();
if (request == null)
throw new ArgumentNullException (nameof (request));

if (!request.RequestUri.IsAbsoluteUri)
throw new ArgumentException ("Must represent an absolute URI", "request");

Expand Down Expand Up @@ -633,7 +669,7 @@ internal Task WriteRequestContentToOutputInternal (HttpRequestMessage request, H
return ret;
}

HttpContent GetErrorContent (HttpURLConnection httpConnection, HttpContent fallbackContent)
HttpContent GetErrorContent (HttpURLConnection httpConnection, HttpContent fallbackContent)
{
var contentStream = httpConnection.ErrorStream;

Expand Down Expand Up @@ -796,7 +832,7 @@ void CollectAuthInfo (HttpHeaderValueCollection <AuthenticationHeaderValue> head

RequestedAuthentication = authData.AsReadOnly ();
}

AuthenticationScheme GetAuthScheme (string scheme)
{
if (String.Compare ("basic", scheme, StringComparison.OrdinalIgnoreCase) == 0)
Expand Down Expand Up @@ -851,15 +887,15 @@ void CopyHeaders (HttpURLConnection httpConnection, HttpResponseMessage response
/// <summary>
/// Configure the <see cref="HttpURLConnection"/> before the request is sent. This method is meant to be overriden
/// by applications which need to perform some extra configuration steps on the connection. It is called with all
/// the request headers set, pre-authentication performed (if applicable) but before the request body is set
/// the request headers set, pre-authentication performed (if applicable) but before the request body is set
/// (e.g. for POST requests). The default implementation in AndroidMessageHandler does nothing.
/// </summary>
/// <param name="request">Request data</param>
/// <param name="conn">Pre-configured connection instance</param>
protected virtual Task SetupRequest (HttpRequestMessage request, HttpURLConnection conn)
{
AssertSelf ();

return Task.CompletedTask;
}

Expand Down Expand Up @@ -905,9 +941,9 @@ internal Task SetupRequestInternal (HttpRequestMessage request, HttpURLConnectio
/// <summary>
/// Create and configure an instance of <see cref="TrustManagerFactory"/>. The <paramref name="keyStore"/> parameter is set to the
/// return value of the <see cref="ConfigureKeyStore"/> method, so it might be null if the application overrode the method and provided
/// no key store. It will not be <c>null</c> when the default implementation is used. The application can return <c>null</c> from this
/// no key store. It will not be <c>null</c> when the default implementation is used. The application can return <c>null</c> from this
/// method in which case AndroidMessageHandler will create its own instance of the trust manager factory provided that the <see cref="TrustCerts"/>
/// list contains at least one valid certificate. If there are no valid certificates and this method returns <c>null</c>, no custom
/// list contains at least one valid certificate. If there are no valid certificates and this method returns <c>null</c>, no custom
/// trust manager will be created since that would make all the HTTPS requests fail.
/// </summary>
/// <returns>The trust manager factory.</returns>
Expand All @@ -930,7 +966,7 @@ void AppendEncoding (string encoding, ref List <string>? list)
return;
list.Add (encoding);
}

async Task <HttpURLConnection> SetupRequestInternal (HttpRequestMessage request, URLConnection conn)
{
if (conn == null)
Expand All @@ -951,15 +987,15 @@ void AppendEncoding (string encoding, ref List <string>? list)
if (request.Content != null)
AddHeaders (httpConnection, request.Content.Headers);
AddHeaders (httpConnection, request.Headers);

List <string>? accept_encoding = null;

decompress_here = false;
if ((AutomaticDecompression & DecompressionMethods.GZip) != 0) {
AppendEncoding (GZIP_ENCODING, ref accept_encoding);
decompress_here = true;
}

if ((AutomaticDecompression & DecompressionMethods.Deflate) != 0) {
AppendEncoding (DEFLATE_ENCODING, ref accept_encoding);
decompress_here = true;
Expand All @@ -978,7 +1014,7 @@ void AppendEncoding (string encoding, ref List <string>? list)
if (!String.IsNullOrEmpty (cookieHeaderValue))
httpConnection.SetRequestProperty ("Cookie", cookieHeaderValue);
}

HandlePreAuthentication (httpConnection);
await SetupRequest (request, httpConnection).ConfigureAwait (continueOnCapturedContext: false);;
SetupRequestBody (httpConnection, request);
Expand Down Expand Up @@ -1035,7 +1071,7 @@ void SetupSSL (HttpsURLConnection? httpsConnection, HttpRequestMessage requestMe
// there is no point in changing the behavior of the default SSL socket factory
if (!gotCerts && _callbackTrustManagerHelper == null)
return;

tmf = TrustManagerFactory.GetInstance (TrustManagerFactory.DefaultAlgorithm);
tmf?.Init (gotCerts ? keyStore : null); // only use the custom key store if the user defined any trusted certs
}
Expand Down Expand Up @@ -1068,7 +1104,7 @@ void SetupSSL (HttpsURLConnection? httpsConnection, HttpRequestMessage requestMe
return keyStore;
}
}

void HandlePreAuthentication (HttpURLConnection httpConnection)
{
var data = PreAuthenticationData;
Expand Down Expand Up @@ -1114,7 +1150,7 @@ void AddHeaders (HttpURLConnection conn, HttpHeaders headers)
conn.SetRequestProperty (header.Key, header.Value != null ? String.Join (GetHeaderSeparator (header.Key), header.Value) : String.Empty);
}
}

void SetupRequestBody (HttpURLConnection httpConnection, HttpRequestMessage request)
{
if (request.Content == null) {
Expand Down
Loading