-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Improve usage of Type.GetType
when activating types in data protection
#54256
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
Conversation
1094681
to
e5491bf
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, but let's give @JamesNK time to comment. Thanks!
src/DataProtection/DataProtection/src/XmlEncryption/XmlEncryptionExtensions.cs
Outdated
Show resolved
Hide resolved
// We want to simulate an error loading the specified decryptor type, i.e. | ||
// Could not load file or assembly 'Azure.Extensions.AspNetCore.DataProtection.Keys, | ||
// Version=1.2.2.0, Culture=neutral, PublicKeyToken=92742159e12e44c8' or one of its dependencies. | ||
XmlEncryptionExtensions._getType = _ => throw new TypeLoadException(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I worry about static state like this impacting other unit tests. It's not being reset so unit tests run afterwards would run this func. And if it was reset, there is still the possibility of concurrent tests.
Is it necessary? Can a value be specified that would trigger TypeLoadException without having to override how the GetType call worked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did try to find a way to produce a FileLoadException
or TypeLoadException
by modifying the type, namespace, assembly, version etc. of the decryptorType
, but wasn't able to; no matter what I did, it kept returning null
instead of throwing.
We're probably seeing this more because we're using .NET Framework with (semi-)manual binding redirects, while .NET (Core) seems much more forgiving.
If you have any suggestions, I'm all ears 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not having a unit test is one solution. However, when I wrote this code, I didn't consider that an error could be thrown here. Catching it is a band aid.
Maybe the solution here is to match to these known types from a string without using Type.GetType
. If the type name and assembly match, then we go directly to the known types. We'd be getting into the type name parsing business though, which is scary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.NET Core probably changed the behavior so that
throwOnError
was always respected.
It's still documented to throw under the same circumstances, but .NET (Core) is much more resilient when it comes to resolving multiple versions of the same assemblies.
Not having a unit test is one solution. However, when I wrote this code I didn't consider that an error could be thrown here. Catching it is a band aid.
Not necessarily - you're accepting a string "user input" and passing it to a function that could throw in some documented cases. I think it's Totally Fine™ to catch the exception and have a fallback behavior.
Maybe the solution here is to match to these known types from a string without using Type.GetType. If the type name and assembly match, then we go directly to the known types. We'd be getting into the type name parsing business though, which is scary.
Yeah, why were the type checks introduced in the first place? I see there's a similar issue in XmlKeyManager.CreateDeserializer
.
I have a crazy idea; what about an internal (or maybe even public?) interface
internal interface ITypeForwardingActivator : IActivator
{
bool TryForwardType(string originalTypeName, [NotNullWhen(true)] out Type? type);
}
which could be checked by pattern matching
private static IXmlDecryptor CreateDecryptor(IActivator activator, string decryptorTypeName)
{
if (activator is ITypeForwardingActivator typeForwarder && typeForwarder.TryForwardType(decryptorTypeName, out var type))
{
if (type == typeof(DpapiNGXmlDecryptor))
{
return activator.CreateInstance<DpapiNGXmlDecryptor>(decryptorTypeName);
}
else if (type == typeof(DpapiXmlDecryptor))
{
return activator.CreateInstance<DpapiXmlDecryptor>(decryptorTypeName);
}
else if (type == typeof(EncryptedXmlDecryptor))
{
return activator.CreateInstance<EncryptedXmlDecryptor>(decryptorTypeName);
}
else if (type == typeof(NullXmlDecryptor))
{
return activator.CreateInstance<NullXmlDecryptor>(decryptorTypeName);
}
}
return activator.CreateInstance<IXmlDecryptor>(decryptorTypeName);
}
This would be implemented by the default TypeForwardingActivator
and reuse the same existing logic.
Then the unit test could provide its own faked/mocked activator that returns false
from TryForwardType
through the IActivator
argument?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've pushed a commit to show what it would look like 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But the "what are we even testing at this point?" question is becoming more and more relevant 😆
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JamesNK What do you think about the approach? Keep it or just keep it simple and remove the test altogether?
Sorry for the delay. I looked at the latest updates. A problem I see is that custom activators won't have Another problem is type names get resolved over and over which is bad if I made some changes that I think solves these two issues. Can you check out: https://github.com/dotnet/aspnetcore/compare/jamesnk/check-type-name-before-resolve Changes:
What do you think? If you're happy with the changes I can start a new PR or you're welcome to update this one. |
912b7c1
to
07f7f17
Compare
07f7f17
to
c695ea0
Compare
LGTM. I'm most concerned about getting the bug patched 😄 I've rebased the branch, picked up your commit and removed a now-unnecessary suppression 👍 |
Do you need this patched in a 8.0 servicing release? @amcasey Can you take another look. |
That would be awesome. Otherwise we'd have to wait until november? 😅 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally looks good, but I had some questions.
src/DataProtection/DataProtection/src/KeyManagement/XmlKeyManager.cs
Outdated
Show resolved
Hide resolved
src/DataProtection/DataProtection/src/KeyManagement/XmlKeyManager.cs
Outdated
Show resolved
Hide resolved
src/DataProtection/DataProtection/src/KeyManagement/XmlKeyManager.cs
Outdated
Show resolved
Hide resolved
...test/Microsoft.AspNetCore.DataProtection.Tests/XmlEncryption/XmlEncryptionExtensionsTests.cs
Show resolved
Hide resolved
...test/Microsoft.AspNetCore.DataProtection.Tests/XmlEncryption/XmlEncryptionExtensionsTests.cs
Outdated
Show resolved
Hide resolved
...test/Microsoft.AspNetCore.DataProtection.Tests/XmlEncryption/XmlEncryptionExtensionsTests.cs
Outdated
Show resolved
Hide resolved
...test/Microsoft.AspNetCore.DataProtection.Tests/XmlEncryption/XmlEncryptionExtensionsTests.cs
Show resolved
Hide resolved
@amcasey PR feedback applied. |
...test/Microsoft.AspNetCore.DataProtection.Tests/XmlEncryption/XmlEncryptionExtensionsTests.cs
Outdated
Show resolved
Hide resolved
...test/Microsoft.AspNetCore.DataProtection.Tests/XmlEncryption/XmlEncryptionExtensionsTests.cs
Outdated
Show resolved
Hide resolved
…taProtection.Tests/XmlEncryption/XmlEncryptionExtensionsTests.cs
IActivator.CreateInstance<IXmlDecryptor>
when Type.GetType
failsType.GetType
when activating types in data protection
I see there's an 8.0.3 version of the data protection packages out. Was this ever backported? |
This missed the deadline for 8.0.4. We'll see about getting it into 8.0.5. There is a long lead time for servicing. |
/backport to release/8.0 |
Started backporting to release/8.0: https://github.com/dotnet/aspnetcore/actions/runs/8429820737 |
Fixes #54253
Fixes #48910
There are some documented cases where
Type.GetType(<name>, throwOnError: false)
can still throw:This happens in the case where a
decryptorType
of previous version of an assembly has been used and prevents a customIActivator
implementation from doing proper forwarding.This PR handles this case by catching errors from
Type.GetType
and letting the activator handle it instead.Another way of solving this could be to make
TypeForwardingActivator
public
and expose a way to register other namespaces to forward. Today, theDecryptorTypeForwardingActivator
type inAzure.Extensions.AspNetCore.DataProtection.Keys
is more or less an outdated copy of theTypeForwardingActivator
in this repo.// @JamesNK @amcasey