Skip to content
This repository was archived by the owner on Oct 17, 2018. It is now read-only.

Commit 02f122a

Browse files
author
Nate McMaster
committed
Ignore assembly version when activating DataProtection types from string name on .NET Framework
This is a port of #223 from the 2.0.0 release. It resolves an issue caused by activating types by type name. Type.GetType enforces the assembly version. This change strips assembly version before attempting to re-create DataProtection types.
1 parent 013b5aa commit 02f122a

File tree

6 files changed

+253
-93
lines changed

6 files changed

+253
-93
lines changed

src/Microsoft.AspNetCore.DataProtection/DataProtectionServiceCollectionExtensions.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public static IDataProtectionBuilder AddDataProtection(this IServiceCollection s
2424
throw new ArgumentNullException(nameof(services));
2525
}
2626

27-
services.AddSingleton<IActivator, RC1ForwardingActivator>();
27+
services.AddSingleton<IActivator, TypeForwardingActivator>();
2828
services.AddOptions();
2929
services.TryAdd(DataProtectionServices.GetDefaultServices());
3030

src/Microsoft.AspNetCore.DataProtection/RC1ForwardingActivator.cs

-42
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Text.RegularExpressions;
6+
using Microsoft.Extensions.Logging;
7+
8+
namespace Microsoft.AspNetCore.DataProtection
9+
{
10+
internal class TypeForwardingActivator : SimpleActivator
11+
{
12+
private const string OldNamespace = "Microsoft.AspNet.DataProtection";
13+
private const string CurrentNamespace = "Microsoft.AspNetCore.DataProtection";
14+
private readonly ILogger _logger;
15+
private static readonly Regex _versionPattern = new Regex(@",\s?Version=[0-9]+(\.[0-9]+){0,3}", RegexOptions.Compiled, TimeSpan.FromSeconds(2));
16+
17+
public TypeForwardingActivator(IServiceProvider services)
18+
: this(services, loggerFactory: null)
19+
{
20+
}
21+
22+
public TypeForwardingActivator(IServiceProvider services, ILoggerFactory loggerFactory)
23+
: base(services)
24+
{
25+
_logger = loggerFactory?.CreateLogger(typeof(TypeForwardingActivator));
26+
}
27+
28+
public override object CreateInstance(Type expectedBaseType, string originalTypeName)
29+
{
30+
bool _;
31+
return CreateInstance(expectedBaseType, originalTypeName, out _);
32+
}
33+
34+
// for testing
35+
internal object CreateInstance(Type expectedBaseType, string originalTypeName, out bool forwarded)
36+
{
37+
var forwardedTypeName = originalTypeName;
38+
var candidate = false;
39+
if (originalTypeName.Contains(OldNamespace))
40+
{
41+
candidate = true;
42+
forwardedTypeName = originalTypeName.Replace(OldNamespace, CurrentNamespace);
43+
}
44+
45+
if (candidate || forwardedTypeName.Contains(CurrentNamespace))
46+
{
47+
candidate = true;
48+
forwardedTypeName = RemoveVersionFromAssemblyName(forwardedTypeName);
49+
}
50+
51+
if (candidate)
52+
{
53+
var type = Type.GetType(forwardedTypeName, false);
54+
if (type != null)
55+
{
56+
_logger?.LogDebug("Forwarded activator type request from {FromType} to {ToType}",
57+
originalTypeName,
58+
forwardedTypeName);
59+
forwarded = true;
60+
return base.CreateInstance(expectedBaseType, forwardedTypeName);
61+
}
62+
}
63+
64+
forwarded = false;
65+
return base.CreateInstance(expectedBaseType, originalTypeName);
66+
}
67+
68+
protected string RemoveVersionFromAssemblyName(string forwardedTypeName)
69+
=> _versionPattern.Replace(forwardedTypeName, "");
70+
}
71+
}

src/Microsoft.AspNetCore.DataProtection/project.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"System.Security.Cryptography.X509Certificates": "4.1.0",
4343
"System.Security.Claims": "4.0.1",
4444
"System.Security.Principal.Windows": "4.0.0",
45+
"System.Text.RegularExpressions": "4.1.0",
4546
"System.Xml.XDocument": "4.0.11"
4647
}
4748
}
@@ -55,4 +56,4 @@
5556
],
5657
"xmlDoc": true
5758
}
58-
}
59+
}

test/Microsoft.AspNetCore.DataProtection.Test/RC1ForwardingActivatorTests.cs

-49
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Reflection;
7+
using Microsoft.Extensions.DependencyInjection;
8+
using Xunit;
9+
10+
namespace Microsoft.AspNetCore.DataProtection
11+
{
12+
#if NET451
13+
public class TypeForwardingActivatorTests : MarshalByRefObject
14+
#elif NETCOREAPP1_0
15+
public class TypeForwardingActivatorTests
16+
#else
17+
#error Target framework should be updated
18+
#endif
19+
{
20+
[Fact]
21+
public void CreateInstance_ForwardsToNewNamespaceIfExists()
22+
{
23+
// Arrange
24+
var serviceCollection = new ServiceCollection();
25+
serviceCollection.AddDataProtection();
26+
var services = serviceCollection.BuildServiceProvider();
27+
var activator = services.GetActivator();
28+
29+
// Act
30+
var name = "Microsoft.AspNet.DataProtection.TypeForwardingActivatorTests+ClassWithParameterlessCtor, Microsoft.AspNet.DataProtection.Test, Version=1.0.0.0";
31+
var instance = activator.CreateInstance<object>(name);
32+
33+
// Assert
34+
Assert.IsType<ClassWithParameterlessCtor>(instance);
35+
}
36+
37+
[Fact]
38+
public void CreateInstance_DoesNotForwardIfClassDoesNotExist()
39+
{
40+
// Arrange
41+
var serviceCollection = new ServiceCollection();
42+
serviceCollection.AddDataProtection();
43+
var services = serviceCollection.BuildServiceProvider();
44+
var activator = services.GetActivator();
45+
46+
// Act & Assert
47+
var name = "Microsoft.AspNet.DataProtection.TypeForwardingActivatorTests+NonExistentClassWithParameterlessCtor, Microsoft.AspNet.DataProtection.Test";
48+
var exception = Assert.ThrowsAny<Exception>(() => activator.CreateInstance<object>(name));
49+
50+
Assert.Contains("Microsoft.AspNet.DataProtection.Test", exception.Message);
51+
}
52+
53+
[Theory]
54+
[InlineData(typeof(GenericType<GenericType<ClassWithParameterlessCtor>>))]
55+
[InlineData(typeof(GenericType<ClassWithParameterlessCtor>))]
56+
[InlineData(typeof(GenericType<GenericType<string>>))]
57+
[InlineData(typeof(GenericType<GenericType<string, string>>))]
58+
[InlineData(typeof(GenericType<string>))]
59+
[InlineData(typeof(GenericType<int>))]
60+
[InlineData(typeof(List<ClassWithParameterlessCtor>))]
61+
public void CreateInstance_Generics(Type type)
62+
{
63+
// Arrange
64+
var activator = new TypeForwardingActivator(null);
65+
var name = type.AssemblyQualifiedName;
66+
67+
// Act & Assert
68+
Assert.IsType(type, activator.CreateInstance<object>(name));
69+
}
70+
71+
[Theory]
72+
[InlineData(typeof(GenericType<>))]
73+
[InlineData(typeof(GenericType<,>))]
74+
public void CreateInstance_ThrowsForOpenGenerics(Type type)
75+
{
76+
// Arrange
77+
var activator = new TypeForwardingActivator(null);
78+
var name = type.AssemblyQualifiedName;
79+
80+
// Act & Assert
81+
Assert.Throws<ArgumentException>(() => activator.CreateInstance<object>(name));
82+
}
83+
84+
[Theory]
85+
[InlineData(
86+
"System.Tuple`1[[Some.Type, Microsoft.AspNetCore.DataProtection, Version=1.0.0.0, Culture=neutral]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
87+
"System.Tuple`1[[Some.Type, Microsoft.AspNetCore.DataProtection, Culture=neutral]], mscorlib, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
88+
[InlineData(
89+
"Some.Type`1[[System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Microsoft.AspNetCore.DataProtection, Version=1.0.0.0, Culture=neutral",
90+
"Some.Type`1[[System.Int32, mscorlib, Culture=neutral, PublicKeyToken=b77a5c561934e089]], Microsoft.AspNetCore.DataProtection, Culture=neutral")]
91+
[InlineData(
92+
"System.Tuple`1[[System.Tuple`1[[Some.Type, Microsoft.AspNetCore.DataProtection, Version=1.0.0.0, Culture=neutral]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
93+
"System.Tuple`1[[System.Tuple`1[[Some.Type, Microsoft.AspNetCore.DataProtection, Culture=neutral]], mscorlib, Culture=neutral, PublicKeyToken=b77a5c561934e089]], mscorlib, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
94+
public void ParsesFullyQualifiedTypeName(string typeName, string expected)
95+
{
96+
Assert.Equal(expected, new MockTypeForwardingActivator().Parse(typeName));
97+
}
98+
99+
[Theory]
100+
[InlineData(typeof(List<string>))]
101+
[InlineData(typeof(FactAttribute))]
102+
public void CreateInstance_DoesNotForwardingTypesExternalTypes(Type type)
103+
{
104+
bool forwarded;
105+
new TypeForwardingActivator(null).CreateInstance(typeof(object), type.AssemblyQualifiedName, out forwarded);
106+
Assert.False(forwarded, "Should not have forwarded types that are not in Microsoft.AspNetCore.DataProjection");
107+
}
108+
109+
[Theory]
110+
[MemberData(nameof(AssemblyVersions))]
111+
public void CreateInstance_ForwardsAcrossVersionChanges(Version version)
112+
{
113+
#if NET451
114+
// run this test in an appdomain without testhost's custom assembly resolution hooks
115+
var setupInfo = new AppDomainSetup
116+
{
117+
ApplicationBase = AppDomain.CurrentDomain.BaseDirectory
118+
};
119+
var domain = AppDomain.CreateDomain("TestDomain", null, setupInfo);
120+
var wrappedTestClass = (TypeForwardingActivatorTests)domain.CreateInstanceAndUnwrap(GetType().Assembly.FullName, typeof(TypeForwardingActivatorTests).FullName);
121+
wrappedTestClass.CreateInstance_ForwardsAcrossVersionChangesImpl(version);
122+
#elif NETCOREAPP1_0
123+
CreateInstance_ForwardsAcrossVersionChangesImpl(version);
124+
#else
125+
#error Target framework should be updated
126+
#endif
127+
}
128+
129+
private void CreateInstance_ForwardsAcrossVersionChangesImpl(Version newVersion)
130+
{
131+
var activator = new TypeForwardingActivator(null);
132+
133+
var typeInfo = typeof(ClassWithParameterlessCtor).GetTypeInfo();
134+
var typeName = typeInfo.FullName;
135+
var assemblyName = typeInfo.Assembly.GetName();
136+
137+
assemblyName.Version = newVersion;
138+
var newName = $"{typeName}, {assemblyName}";
139+
140+
Assert.NotEqual(typeInfo.AssemblyQualifiedName, newName);
141+
bool forwarded;
142+
Assert.IsType<ClassWithParameterlessCtor>(activator.CreateInstance(typeof(object), newName, out forwarded));
143+
Assert.True(forwarded, "Should have forwarded this type to new version or namespace");
144+
}
145+
146+
public static TheoryData<Version> AssemblyVersions
147+
{
148+
get
149+
{
150+
var current = typeof(ActivatorTests).GetTypeInfo().Assembly.GetName().Version;
151+
return new TheoryData<Version>
152+
{
153+
new Version(Math.Max(0, current.Major - 1), 0, 0, 0),
154+
new Version(current.Major + 1, 0, 0, 0),
155+
new Version(current.Major, current.Minor + 1, 0, 0),
156+
new Version(current.Major, current.Minor, current.Revision + 1, 0),
157+
};
158+
}
159+
}
160+
161+
private class MockTypeForwardingActivator : TypeForwardingActivator
162+
{
163+
public MockTypeForwardingActivator() : base(null) { }
164+
public string Parse(string typeName) => RemoveVersionFromAssemblyName(typeName);
165+
}
166+
167+
private class ClassWithParameterlessCtor
168+
{
169+
}
170+
171+
private class GenericType<T>
172+
{
173+
}
174+
175+
private class GenericType<T1, T2>
176+
{
177+
}
178+
}
179+
}

0 commit comments

Comments
 (0)