Skip to content

Commit 1f76260

Browse files
authored
Merge pull request #107 from TomPallister/feature/tokens-work-across-ocelots
Feature/tokens work across ocelots
2 parents bc7bfc8 + e96d661 commit 1f76260

15 files changed

+242
-9
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ ClientBin/
183183
*.dbmdl
184184
*.dbproj.schemaview
185185
*.pfx
186+
!idsrv3test.pfx
186187
*.publishsettings
187188
node_modules/
188189
orleans.codegen.cs

src/Ocelot/Authentication/Handler/Creator/AuthenticationHandlerCreator.cs

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using IdentityServer4.AccessTokenValidation;
23
using Microsoft.AspNetCore.Builder;
34
using Microsoft.AspNetCore.Http;

src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ public static IdentityServerConfiguration GetIdentityServerConfiguration()
1313
var username = Environment.GetEnvironmentVariable("OCELOT_USERNAME");
1414
var hash = Environment.GetEnvironmentVariable("OCELOT_HASH");
1515
var salt = Environment.GetEnvironmentVariable("OCELOT_SALT");
16+
var credentialsSigningCertificateLocation = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE");
17+
var credentialsSigningCertificatePassword = Environment.GetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD");
1618

1719
return new IdentityServerConfiguration(
1820
"admin",
@@ -28,7 +30,9 @@ public static IdentityServerConfiguration GetIdentityServerConfiguration()
2830
new List<User>
2931
{
3032
new User("admin", username, hash, salt)
31-
}
33+
},
34+
credentialsSigningCertificateLocation,
35+
credentialsSigningCertificatePassword
3236
);
3337
}
3438
}

src/Ocelot/Configuration/Provider/IIdentityServerConfiguration.cs

+2
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,7 @@ public interface IIdentityServerConfiguration
1717
AccessTokenType AccessTokenType {get;}
1818
bool RequireClientSecret {get;}
1919
List<User> Users {get;}
20+
string CredentialsSigningCertificateLocation { get; }
21+
string CredentialsSigningCertificatePassword { get; }
2022
}
2123
}

src/Ocelot/Configuration/Provider/IdentityServerConfiguration.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public IdentityServerConfiguration(
1717
IEnumerable<string> grantType,
1818
AccessTokenType accessTokenType,
1919
bool requireClientSecret,
20-
List<User> users)
20+
List<User> users, string credentialsSigningCertificateLocation, string credentialsSigningCertificatePassword)
2121
{
2222
ApiName = apiName;
2323
RequireHttps = requireHttps;
@@ -30,6 +30,8 @@ public IdentityServerConfiguration(
3030
AccessTokenType = accessTokenType;
3131
RequireClientSecret = requireClientSecret;
3232
Users = users;
33+
CredentialsSigningCertificateLocation = credentialsSigningCertificateLocation;
34+
CredentialsSigningCertificatePassword = credentialsSigningCertificatePassword;
3335
}
3436

3537
public string ApiName { get; private set; }
@@ -43,5 +45,7 @@ public IdentityServerConfiguration(
4345
public AccessTokenType AccessTokenType {get;private set;}
4446
public bool RequireClientSecret {get;private set;}
4547
public List<User> Users {get;private set;}
48+
public string CredentialsSigningCertificateLocation { get; private set; }
49+
public string CredentialsSigningCertificatePassword { get; private set; }
4650
}
4751
}

src/Ocelot/DependencyInjection/ServiceCollectionExtensions.cs

+16-2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
using System.Linq;
4242
using System.Net.Http;
4343
using System.Reflection;
44+
using System.Security.Cryptography.X509Certificates;
45+
using Microsoft.IdentityModel.Tokens;
4446
using Ocelot.Configuration;
4547
using FileConfigurationProvider = Ocelot.Configuration.Provider.FileConfigurationProvider;
4648

@@ -87,8 +89,10 @@ public static IServiceCollection AddOcelot(this IServiceCollection services, ICo
8789
{
8890
services.TryAddSingleton<IIdentityServerConfiguration>(identityServerConfiguration);
8991
services.TryAddSingleton<IHashMatcher, HashMatcher>();
90-
services.AddIdentityServer()
91-
.AddTemporarySigningCredential()
92+
var identityServerBuilder = services
93+
.AddIdentityServer(options => {
94+
options.IssuerUri = "Ocelot";
95+
})
9296
.AddInMemoryApiResources(new List<ApiResource>
9397
{
9498
new ApiResource
@@ -120,6 +124,16 @@ public static IServiceCollection AddOcelot(this IServiceCollection services, ICo
120124
RequireClientSecret = identityServerConfiguration.RequireClientSecret
121125
}
122126
}).AddResourceOwnerValidator<OcelotResourceOwnerPasswordValidator>();
127+
128+
if (string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificateLocation) || string.IsNullOrEmpty(identityServerConfiguration.CredentialsSigningCertificatePassword))
129+
{
130+
identityServerBuilder.AddTemporarySigningCredential();
131+
}
132+
else
133+
{
134+
var cert = new X509Certificate2(identityServerConfiguration.CredentialsSigningCertificateLocation, identityServerConfiguration.CredentialsSigningCertificatePassword);
135+
identityServerBuilder.AddSigningCredential(cert);
136+
}
123137
}
124138

125139
var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly;

src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs

-1
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,6 @@ private static async Task CreateAdministrationArea(IApplicationBuilder builder)
181181
builder.Map(configuration.AdministrationPath, app =>
182182
{
183183
var identityServerUrl = $"{baseSchemeUrlAndPort}/{configuration.AdministrationPath.Remove(0,1)}";
184-
185184
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
186185
{
187186
Authority = identityServerUrl,

test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
</PropertyGroup>
1616

1717
<ItemGroup>
18-
<None Update="configuration.json">
18+
<None Update="configuration.json;appsettings.json">
1919
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
2020
</None>
2121
</ItemGroup>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"Logging": {
3+
"IncludeScopes": true,
4+
"LogLevel": {
5+
"Default": "Error",
6+
"System": "Error",
7+
"Microsoft": "Error"
8+
}
9+
}
10+
}

test/Ocelot.IntegrationTests/AdministrationTests.cs

+57
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,19 @@ namespace Ocelot.IntegrationTests
1919
public class AdministrationTests : IDisposable
2020
{
2121
private readonly HttpClient _httpClient;
22+
private readonly HttpClient _httpClientTwo;
2223
private HttpResponseMessage _response;
2324
private IWebHost _builder;
2425
private IWebHostBuilder _webHostBuilder;
2526
private readonly string _ocelotBaseUrl;
2627
private BearerToken _token;
28+
private IWebHostBuilder _webHostBuilderTwo;
29+
private IWebHost _builderTwo;
2730

2831
public AdministrationTests()
2932
{
3033
_httpClient = new HttpClient();
34+
_httpClientTwo = new HttpClient();
3135
_ocelotBaseUrl = "http://localhost:5000";
3236
_httpClient.BaseAddress = new Uri(_ocelotBaseUrl);
3337
}
@@ -70,6 +74,27 @@ public void should_return_response_200_with_call_re_routes_controller()
7074
.BDDfy();
7175
}
7276

77+
[Fact]
78+
public void should_be_able_to_use_token_from_ocelot_a_on_ocelot_b()
79+
{
80+
var configuration = new FileConfiguration
81+
{
82+
GlobalConfiguration = new FileGlobalConfiguration
83+
{
84+
AdministrationPath = "/administration"
85+
}
86+
};
87+
88+
this.Given(x => GivenThereIsAConfiguration(configuration))
89+
.And(x => GivenIdentityServerSigningEnvironmentalVariablesAreSet())
90+
.And(x => GivenOcelotIsRunning())
91+
.And(x => GivenIHaveAnOcelotToken("/administration"))
92+
.And(x => GivenAnotherOcelotIsRunning("http://localhost:5007"))
93+
.When(x => WhenIGetUrlOnTheSecondOcelot("/administration/configuration"))
94+
.Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK))
95+
.BDDfy();
96+
}
97+
7398
[Fact]
7499
public void should_return_file_configuration()
75100
{
@@ -193,6 +218,36 @@ public void should_get_file_configuration_edit_and_post_updated_version()
193218
.BDDfy();
194219
}
195220

221+
private void GivenAnotherOcelotIsRunning(string baseUrl)
222+
{
223+
_httpClientTwo.BaseAddress = new Uri(baseUrl);
224+
225+
_webHostBuilderTwo = new WebHostBuilder()
226+
.UseUrls(baseUrl)
227+
.UseKestrel()
228+
.UseContentRoot(Directory.GetCurrentDirectory())
229+
.ConfigureServices(x => {
230+
x.AddSingleton(_webHostBuilderTwo);
231+
})
232+
.UseStartup<Startup>();
233+
234+
_builderTwo = _webHostBuilderTwo.Build();
235+
236+
_builderTwo.Start();
237+
}
238+
239+
private void GivenIdentityServerSigningEnvironmentalVariablesAreSet()
240+
{
241+
Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE", "idsrv3test.pfx");
242+
Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", "idsrv3test");
243+
}
244+
245+
private void WhenIGetUrlOnTheSecondOcelot(string url)
246+
{
247+
_httpClientTwo.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken);
248+
_response = _httpClientTwo.GetAsync(url).Result;
249+
}
250+
196251
private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration)
197252
{
198253
var json = JsonConvert.SerializeObject(updatedConfiguration);
@@ -305,6 +360,8 @@ private void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode)
305360

306361
public void Dispose()
307362
{
363+
Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE", "");
364+
Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", "");
308365
_builder?.Dispose();
309366
_httpClient?.Dispose();
310367
}

test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
</PropertyGroup>
1616

1717
<ItemGroup>
18-
<None Update="configuration.json;appsettings.json">
18+
<None Update="configuration.json;appsettings.json;idsrv3test.pfx">
1919
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
2020
</None>
2121
</ItemGroup>

test/Ocelot.IntegrationTests/appsettings.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
"IncludeScopes": true,
44
"LogLevel": {
55
"Default": "Error",
6-
"System": "Information",
7-
"Microsoft": "Information"
6+
"System": "Error",
7+
"Microsoft": "Error"
88
}
99
}
1010
}
3.32 KB
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using Ocelot.Configuration.Creator;
2+
using Shouldly;
3+
using Xunit;
4+
5+
namespace Ocelot.UnitTests.Configuration
6+
{
7+
public class IdentityServerConfigurationCreatorTests
8+
{
9+
[Fact]
10+
public void happy_path_only_exists_for_test_coverage_even_uncle_bob_probably_wouldnt_test_this()
11+
{
12+
var result = IdentityServerConfigurationCreator.GetIdentityServerConfiguration();
13+
result.ApiName.ShouldBe("admin");
14+
}
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
using System;
2+
using System.IO;
3+
using System.Net;
4+
using System.Net.Http;
5+
using Microsoft.AspNetCore.Builder;
6+
using Microsoft.AspNetCore.Hosting;
7+
using Microsoft.AspNetCore.TestHost;
8+
using Microsoft.Extensions.DependencyInjection;
9+
using Moq;
10+
using Ocelot.DownstreamRouteFinder;
11+
using Ocelot.DownstreamUrlCreator;
12+
using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer;
13+
using Ocelot.Errors.Middleware;
14+
using Ocelot.Infrastructure.RequestData;
15+
using Ocelot.Logging;
16+
using Ocelot.Responses;
17+
using Ocelot.Values;
18+
using Shouldly;
19+
using TestStack.BDDfy;
20+
using Xunit;
21+
22+
namespace Ocelot.UnitTests.Errors
23+
{
24+
public class ExceptionHandlerMiddlewareTests
25+
{
26+
private readonly Mock<IRequestScopedDataRepository> _scopedRepository;
27+
private readonly string _url;
28+
private TestServer _server;
29+
private HttpClient _client;
30+
private HttpResponseMessage _result;
31+
32+
public ExceptionHandlerMiddlewareTests()
33+
{
34+
_url = "http://localhost:52879";
35+
_scopedRepository = new Mock<IRequestScopedDataRepository>();
36+
}
37+
38+
[Fact]
39+
public void should_call_next_middleware()
40+
{
41+
this.Given(_ => GivenASuccessfulRequest())
42+
.When(_ => WhenIMakeTheRequest())
43+
.Then(_ => ThenTheResponseIsOk())
44+
.BDDfy();
45+
}
46+
47+
[Fact]
48+
public void should_call_return_error()
49+
{
50+
this.Given(_ => GivenAnError())
51+
.When(_ => WhenIMakeTheRequest())
52+
.Then(_ => ThenTheResponseIsError())
53+
.BDDfy();
54+
}
55+
56+
private void ThenTheResponseIsOk()
57+
{
58+
_result.StatusCode.ShouldBe(HttpStatusCode.OK);
59+
}
60+
61+
private void ThenTheResponseIsError()
62+
{
63+
_result.StatusCode.ShouldBe(HttpStatusCode.InternalServerError);
64+
}
65+
66+
private void WhenIMakeTheRequest()
67+
{
68+
_result = _client.GetAsync("/").Result;
69+
}
70+
71+
private void GivenASuccessfulRequest()
72+
{
73+
var builder = new WebHostBuilder()
74+
.ConfigureServices(x =>
75+
{
76+
x.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
77+
x.AddLogging();
78+
x.AddSingleton(_scopedRepository.Object);
79+
})
80+
.UseUrls(_url)
81+
.UseKestrel()
82+
.UseContentRoot(Directory.GetCurrentDirectory())
83+
.UseIISIntegration()
84+
.UseUrls(_url)
85+
.Configure(app =>
86+
{
87+
app.UseExceptionHandlerMiddleware();
88+
app.Run(async context =>
89+
{
90+
context.Response.StatusCode = 200;
91+
});
92+
});
93+
94+
_server = new TestServer(builder);
95+
_client = _server.CreateClient();
96+
}
97+
98+
private void GivenAnError()
99+
{
100+
var builder = new WebHostBuilder()
101+
.ConfigureServices(x =>
102+
{
103+
x.AddSingleton<IOcelotLoggerFactory, AspDotNetLoggerFactory>();
104+
x.AddLogging();
105+
x.AddSingleton(_scopedRepository.Object);
106+
})
107+
.UseUrls(_url)
108+
.UseKestrel()
109+
.UseContentRoot(Directory.GetCurrentDirectory())
110+
.UseIISIntegration()
111+
.UseUrls(_url)
112+
.Configure(app =>
113+
{
114+
app.UseExceptionHandlerMiddleware();
115+
app.Use(async (context, next) =>
116+
{
117+
throw new Exception("BOOM");
118+
});
119+
});
120+
121+
_server = new TestServer(builder);
122+
_client = _server.CreateClient();
123+
}
124+
}
125+
}

0 commit comments

Comments
 (0)