Skip to content

Commit af189ad

Browse files
authored
Merge pull request #41 from lbroudoux/feat/add_oidc_support
Add contract testing for OIDC/OAuth2 protected resources
2 parents 462bbcf + ba7b1e6 commit af189ad

File tree

7 files changed

+469
-0
lines changed

7 files changed

+469
-0
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//
2+
// Copyright The Microcks Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License")
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
//
17+
18+
using System.Text.Json.Serialization;
19+
20+
namespace Microcks.Testcontainers.Model;
21+
22+
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
23+
24+
/// <summary>
25+
/// Persisted information for an OAuth2 authorization/authentication done before launching a test.
26+
/// </summary>
27+
public class OAuth2AuthorizedClient
28+
{
29+
[JsonPropertyName("principalName")]
30+
public string PrincipalName { get; set; }
31+
32+
[JsonPropertyName("tokenUri")]
33+
public string TokenUri { get; set; }
34+
35+
[JsonPropertyName("scopes")]
36+
public string Scopes { get; set; }
37+
38+
[JsonPropertyName("grantType")]
39+
public OAuth2GrantType GrantType { get; set; }
40+
}

src/Microcks.Testcontainers/Model/OAuth2ClientContext.cs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,104 @@
1515
//
1616
//
1717

18+
using System.Text.Json.Serialization;
19+
1820
namespace Microcks.Testcontainers.Model;
1921

2022
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
23+
24+
/// <summary>
25+
/// A volatile OAuth2 client context usually associated with a Test request.
26+
/// </summary>
2127
public class OAuth2ClientContext
2228
{
29+
[JsonPropertyName("clientId")]
30+
public string ClientId { get; set; }
31+
32+
[JsonPropertyName("clientSecret")]
33+
public string ClientSecret { get; set; }
34+
35+
[JsonPropertyName("tokenUri")]
36+
public string TokenUri { get; set; }
37+
38+
[JsonPropertyName("scopes")]
39+
public string Scopes { get; set; }
40+
41+
[JsonPropertyName("username")]
42+
public string Username { get; set; }
43+
44+
[JsonPropertyName("password")]
45+
public string Password { get; set; }
46+
47+
[JsonPropertyName("refreshToken")]
48+
public string RefreshToken { get; set; }
49+
50+
[JsonPropertyName("grantType")]
51+
public OAuth2GrantType GrantType { get; set; }
52+
}
53+
54+
/// <summary>
55+
/// A Builder to create OAuth2ClientContext using a fluid APi.
56+
/// </summary>
57+
public class OAuth2ClientContextBuilder
58+
{
59+
private readonly OAuth2ClientContext _context;
60+
61+
public OAuth2ClientContextBuilder()
62+
{
63+
_context = new OAuth2ClientContext();
64+
}
65+
66+
public OAuth2ClientContextBuilder WithClientId(string clientId)
67+
{
68+
_context.ClientId = clientId;
69+
return this;
70+
}
71+
72+
public OAuth2ClientContextBuilder WithClientSecret(string clientSecret)
73+
{
74+
_context.ClientSecret = clientSecret;
75+
return this;
76+
}
77+
78+
public OAuth2ClientContextBuilder WithTokenUri(string tokenUri)
79+
{
80+
_context.TokenUri = tokenUri;
81+
return this;
82+
}
83+
84+
public OAuth2ClientContextBuilder WithScopes(string scopes)
85+
{
86+
_context.Scopes = scopes;
87+
return this;
88+
}
89+
90+
public OAuth2ClientContextBuilder WithUsername(string username)
91+
{
92+
_context.Username = username;
93+
return this;
94+
}
95+
96+
public OAuth2ClientContextBuilder WithPassword(string password)
97+
{
98+
_context.Password = password;
99+
return this;
100+
}
101+
102+
public OAuth2ClientContextBuilder WithRefreshToken(string refreshToken)
103+
{
104+
_context.RefreshToken = refreshToken;
105+
return this;
106+
}
107+
108+
public OAuth2ClientContextBuilder WithGrantType(OAuth2GrantType grantType)
109+
{
110+
_context.GrantType = grantType;
111+
return this;
112+
}
113+
114+
public OAuth2ClientContext Build()
115+
{
116+
return _context;
117+
}
23118
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//
2+
// Copyright The Microcks Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License")
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
//
17+
18+
using System.Text.Json.Serialization;
19+
20+
namespace Microcks.Testcontainers.Model;
21+
22+
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
23+
24+
/// <summary>
25+
/// Enumeration for the different supported grants/flows of OAuth2.
26+
/// </summary>
27+
[JsonConverter(typeof(JsonStringEnumConverter))]
28+
public enum OAuth2GrantType
29+
{
30+
PASSWORD,
31+
CLIENT_CREDENTIALS,
32+
REFRESH_TOKEN
33+
}

src/Microcks.Testcontainers/Model/TestResult.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,7 @@ public class TestResult
6464

6565
[JsonPropertyName("operationsHeaders")]
6666
public Dictionary<string, List<Header>> OperationsHeaders { get; set; }
67+
68+
[JsonPropertyName("authorizedClient")]
69+
public OAuth2AuthorizedClient AuthorizedClient { get; set; }
6770
}

tests/Microcks.Testcontainers.Tests/Microcks.Testcontainers.Tests.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
<PackageReference Include="RestAssured.Net" Version="4.6.0" />
1212
<PackageReference Include="Confluent.Kafka" Version="2.6.1" />
1313
<PackageReference Include="TestContainers.Kafka" Version="4.1.0" />
14+
<PackageReference Include="TestContainers.Keycloak" Version="4.1.0" />
1415
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
1516
<PackageReference Include="xunit" Version="2.9.2" />
1617
</ItemGroup>
@@ -30,6 +31,9 @@
3031
<None Update="microcks-repository.json">
3132
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
3233
</None>
34+
<None Update="myrealm-realm.json">
35+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
36+
</None>
3337
<None Update="subdir\weather-forecast-openapi.yaml">
3438
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
3539
</None>
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
//
2+
// Copyright The Microcks Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License")
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
//
17+
18+
using DotNet.Testcontainers.Builders;
19+
using DotNet.Testcontainers.Containers;
20+
using DotNet.Testcontainers.Networks;
21+
using Microcks.Testcontainers;
22+
using Microcks.Testcontainers.Model;
23+
using System;
24+
using System.Net;
25+
using Testcontainers.Keycloak;
26+
27+
namespace Testcontainers.Microcks.Tests;
28+
29+
public sealed class MicrocksContractTestingFunctionalityWithOAuth2Tests : IAsyncLifetime
30+
{
31+
private readonly INetwork _network = new NetworkBuilder().Build();
32+
private readonly MicrocksContainer _microcksContainer;
33+
private readonly IContainer _goodImpl;
34+
private readonly KeycloakContainer _keycloak;
35+
36+
private static readonly string GOOD_PASTRY_IMAGE = "quay.io/microcks/contract-testing-demo:02";
37+
38+
public MicrocksContractTestingFunctionalityWithOAuth2Tests()
39+
{
40+
_microcksContainer = new MicrocksBuilder()
41+
.WithNetwork(_network)
42+
.Build();
43+
44+
_goodImpl = new ContainerBuilder()
45+
.WithImage(GOOD_PASTRY_IMAGE)
46+
.WithNetwork(_network)
47+
.WithNetworkAliases("good-impl")
48+
.WithWaitStrategy(Wait.ForUnixContainer().UntilMessageIsLogged(".*Example app listening on port 3002.*"))
49+
.Build();
50+
51+
_keycloak = new KeycloakBuilder()
52+
.WithImage("quay.io/keycloak/keycloak:26.0.0")
53+
.WithNetwork(_network)
54+
.WithNetworkAliases("keycloak")
55+
.WithCommand("--import-realm")
56+
.WithResourceMapping("./myrealm-realm.json", "/opt/keycloak/data/import")
57+
.Build();
58+
}
59+
60+
public Task DisposeAsync()
61+
{
62+
return Task.WhenAll(
63+
_microcksContainer.DisposeAsync().AsTask(),
64+
_keycloak.DisposeAsync().AsTask(),
65+
_goodImpl.DisposeAsync().AsTask(),
66+
_network.DisposeAsync().AsTask()
67+
);
68+
}
69+
70+
public Task InitializeAsync()
71+
{
72+
_microcksContainer.Started +=
73+
(_, _) => _microcksContainer.ImportAsMainArtifact("apipastries-openapi.yaml");
74+
75+
return Task.WhenAll(
76+
_microcksContainer.StartAsync(),
77+
_keycloak.StartAsync(),
78+
_goodImpl.StartAsync()
79+
);
80+
}
81+
82+
[Fact]
83+
public void ShouldConfigRetrieval()
84+
{
85+
var uriBuilder = new UriBuilder(_microcksContainer.GetHttpEndpoint())
86+
{
87+
Path = "/api/keycloak/config"
88+
};
89+
90+
Given()
91+
.When()
92+
.Get(uriBuilder.ToString())
93+
.Then()
94+
.StatusCode(HttpStatusCode.OK);
95+
}
96+
97+
[Fact]
98+
public async Task ShouldReturnSuccess_WhenGoodImplementation()
99+
{
100+
// Switch endpoint to good implementation
101+
var testRequest = new TestRequest
102+
{
103+
ServiceId = "API Pastries:0.0.1",
104+
RunnerType = TestRunnerType.OPEN_API_SCHEMA,
105+
TestEndpoint = "http://good-impl:3002",
106+
Timeout = TimeSpan.FromMilliseconds(2000),
107+
oAuth2Context = new OAuth2ClientContextBuilder()
108+
.WithClientId("myrealm-serviceaccount")
109+
.WithClientSecret("ab54d329-e435-41ae-a900-ec6b3fe15c54")
110+
.WithTokenUri("http://keycloak:8080/realms/myrealm/protocol/openid-connect/token")
111+
.WithGrantType(OAuth2GrantType.CLIENT_CREDENTIALS)
112+
.Build()
113+
};
114+
TestResult testResult = await _microcksContainer.TestEndpointAsync(testRequest);
115+
Assert.True(testResult.Success);
116+
Assert.Equal("http://good-impl:3002", testResult.TestedEndpoint);
117+
Assert.Equal(3, testResult.TestCaseResults.Count);
118+
Assert.Empty(testResult.TestCaseResults[0].TestStepResults[0].Message);
119+
120+
// Ensure test has used a valid OAuth2 client
121+
Assert.NotNull(testResult.AuthorizedClient);
122+
Assert.Equal("myrealm-serviceaccount", testResult.AuthorizedClient.PrincipalName);
123+
Assert.Equal("http://keycloak:8080/realms/myrealm/protocol/openid-connect/token", testResult.AuthorizedClient.TokenUri);
124+
Assert.Equal("openid profile email", testResult.AuthorizedClient.Scopes);
125+
}
126+
}

0 commit comments

Comments
 (0)