Skip to content

add support for Impersonation and Delegation to HttpWebRequest #102038

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

Merged
merged 8 commits into from
May 16, 2024

Conversation

wfurt
Copy link
Member

@wfurt wfurt commented May 9, 2024

fixes #29587, contributes to #100895.

HttpWebRequest has ImpersonationLevel property but we simply silently ignore it since .NET Core beginning.
That creates migration issue for certain customers who still depend on different levels.
This brings the functionality on par with .NET Framework. SocketsHttpHandler does not have API to control it but the underlying NegotiateAuthentication we use can do it all. So I added plumbing for the property and little Reflection to access it. It is still probably way cheaper than anything else we do there. I wanted new code to be used only when somebody explicitly sets it so I changed the default - I feel it is OK since it was ignored anyway.

The test only verifies that the reflection does not throw. I did functional testing on setup @sainath-reddy-gnv kindly provided.

@wfurt wfurt added this to the 9.0.0 milestone May 9, 2024
@wfurt wfurt requested review from filipnavara and a team May 9, 2024 05:30
@wfurt wfurt self-assigned this May 9, 2024
@wfurt wfurt changed the title add support for Impersonation and Delegation to HttpWebrequest add support for Impersonation and Delegation to HttpWebRequest May 9, 2024
Comment on lines 1676 to 1680
{
// This is legacy hack. We want to deal with it only if explicitly set.
var setting = typeof(SocketsHttpHandler).InvokeMember("_settings", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance, null, handler, null);
setting?.GetType().InvokeMember("_impersonationLevel", BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, setting, new object[] { request!.ImpersonationLevel });
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to structure it to be usable with UnsafeAccessor attribute. That's not possible here since HttpConnectionSettings is internal.

I assume InvokeMember is known reflection pattern in ILLink/ILC, right? (/cc @sbomer @MichalStrehovsky)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

InvokeMember is not handled. It will keep everything on SocketsHttpHandler because of the annotation:

[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
public object? InvokeMember(string name, BindingFlags invokeAttr, Binder? binder, object? target, object?[]? args) => InvokeMember(name, invokeAttr, binder, target, args, null, null, null);

It would be preferable to call GetField instead so that we just keep a single field.

The subsequent setting?.GetType().InvokeMember is not trim safe at all, this will break under trimming because we have no clue what type is this reflecting on, like the suppressed warning says. Please don't merge this like that, the warning suppression is a bug.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated. However, it is not clear to me how to proceed on the trimming @MichalStrehovsky. Should I annotate the HttpConnectionSettings somehow?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is HttpConnectionSettings the type we expect as the result of setting?.GetType()? If so replacing that with a typeof (or Type.GetType("TypeName, AssemblyName") if it's inaccessible) followed by GetField("_impersonationLevel")) should work without trimming warnings.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. But it is not public type. As @filipnavara mentioned it is internal to SocketsHttpHandler. It is generally container the handler use to pass the setting around to connection pools.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But it is not public type.

That doesn't matter for Type.GetType. You can use Type.GetType("System.Net.Http.HttpConnectionSettings, System.Net.Http") and it resolves the type just fine.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated.

{
// This is legacy hack. We want to deal with it only if explicitly set.
var setting = typeof(SocketsHttpHandler).InvokeMember("_settings", BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Instance, null, handler, null);
setting?.GetType().InvokeMember("_impersonationLevel", BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, setting, new object[] { request!.ImpersonationLevel });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be add something public to SocketsHttpHandler instead of using private reflection?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what #100895 is asking for. My hesitation comes from

  1. it works only on Windows
  2. It is somewhat deprecated and it no longer works by default in new browsers e.g. IE was fine and Edge is not.

So my primary concern was support for legacy app migration.
If we see value, adding public property would not be too difficult.
Let me know @stephentoub and we can push #100895 forward for SocketsHttpHandler.

We still may proceed with this so testing is possible and make updates once public API is available.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We still may proceed with this so testing is possible and make updates once public API is available.

Ok. I don't love the private reflection, but as the components are both in the shared framework and versioned together, I can tolerate it.

it works only on Windows, It is somewhat deprecated and it no longer works by default in new browsers e.g. IE was fine and Edge is not.

Ok, then I'd agree it's not desirable to expose.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -29,6 +30,7 @@ internal sealed class HttpConnectionSettings

internal bool _preAuthenticate = HttpHandlerDefaults.DefaultPreAuthenticate;
internal ICredentials? _credentials;
internal TokenImpersonationLevel _impersonationLevel = HttpHandlerDefaults.DefaultImpersonationLevel;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It'd be worth a comment that this is used via reflection from WebRequest.

@wfurt
Copy link
Member Author

wfurt commented May 16, 2024

all test failures are known errors.

@wfurt wfurt merged commit ffad857 into dotnet:main May 16, 2024
80 of 83 checks passed
@wfurt wfurt deleted the imp branch May 16, 2024 18:00
Ruihan-Yin pushed a commit to Ruihan-Yin/runtime that referenced this pull request May 30, 2024
…t#102038)

* initial drop

* update

* feedback

* feedbcak

* comment

* spacing
@github-actions github-actions bot locked and limited conversation to collaborators Jun 16, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
4 participants