Skip to content

Commit b7d43a2

Browse files
committed
oidc-server-mock: Improved extensibility by creating abstract classes
1 parent 0cde338 commit b7d43a2

File tree

7 files changed

+556
-400
lines changed

7 files changed

+556
-400
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 2.4.0
2+
* ``oidc-server-mock``
3+
* Improved extensibility by creating abstract base classes
4+
15
# 2.3.0
26
* Modularized ``db-jdbc-orm`` into ``db-jdbc``, ``db-jdbc-spring-orm`` and ``db-jdbc-spring-orm-hibernate`` #330
37
* Packages might be slightly different
Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
/*
2+
* Copyright © 2025 XDEV Software (https://xdev.software)
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+
package software.xdev.tci.oidc;
17+
18+
import java.io.IOException;
19+
import java.io.UncheckedIOException;
20+
import java.net.URI;
21+
import java.net.http.HttpClient;
22+
import java.net.http.HttpRequest;
23+
import java.net.http.HttpResponse;
24+
import java.time.Duration;
25+
import java.util.UUID;
26+
27+
import org.apache.hc.client5.http.classic.methods.HttpPost;
28+
import org.apache.hc.client5.http.config.ConnectionConfig;
29+
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
30+
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
31+
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder;
32+
import org.apache.hc.core5.http.ClassicHttpResponse;
33+
import org.apache.hc.core5.http.ContentType;
34+
import org.apache.hc.core5.http.HttpHeaders;
35+
import org.apache.hc.core5.http.HttpStatus;
36+
import org.apache.hc.core5.http.io.entity.StringEntity;
37+
import org.apache.hc.core5.util.Timeout;
38+
import org.rnorth.ducttape.unreliables.Unreliables;
39+
40+
import software.xdev.tci.TCI;
41+
import software.xdev.tci.envperf.EnvironmentPerformance;
42+
import software.xdev.tci.misc.http.HttpClientCloser;
43+
import software.xdev.tci.oidc.containers.BaseOIDCServerContainer;
44+
import software.xdev.tci.oidc.containers.OIDCServerContainer;
45+
46+
47+
@SuppressWarnings("java:S119")
48+
public abstract class BaseOIDCTCI<
49+
SELF extends BaseOIDCTCI<SELF, C>,
50+
C extends BaseOIDCServerContainer<C>>
51+
extends TCI<C>
52+
{
53+
protected static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(30);
54+
55+
public static final String CLIENT_ID = OIDCServerContainer.DEFAULT_CLIENT_ID;
56+
public static final String CLIENT_SECRET = OIDCServerContainer.DEFAULT_CLIENT_SECRET;
57+
58+
public static final String DEFAULT_DOMAIN = "example.local";
59+
60+
public static final String DEFAULT_USER_EMAIL = "test@" + DEFAULT_DOMAIN;
61+
public static final String DEFAULT_USER_NAME = "Testuser";
62+
public static final String DEFAULT_USER_PASSWORD = "pwd";
63+
64+
protected boolean shouldAddDefaultUser = true;
65+
protected String defaultUserEmail = DEFAULT_USER_EMAIL;
66+
protected String defaultUserName = DEFAULT_USER_NAME;
67+
protected String defaultUserPassword = DEFAULT_USER_PASSWORD;
68+
69+
protected BaseOIDCTCI(final C container, final String networkAlias)
70+
{
71+
super(container, networkAlias);
72+
}
73+
74+
@Override
75+
public void start(final String containerName)
76+
{
77+
super.start(containerName);
78+
if(this.shouldAddDefaultUser)
79+
{
80+
this.addUser(this.getDefaultUserEmail(), this.getDefaultUserName(), this.getDefaultUserPassword());
81+
}
82+
83+
// Warm up; Otherwise slow initial response may cause a timeout during tests
84+
this.warmUpWellKnownJWKsEndpoint();
85+
}
86+
87+
public String getDefaultUserEmail()
88+
{
89+
return this.defaultUserEmail;
90+
}
91+
92+
public String getDefaultUserName()
93+
{
94+
return this.defaultUserName;
95+
}
96+
97+
public String getDefaultUserPassword()
98+
{
99+
return this.defaultUserPassword;
100+
}
101+
102+
public static String getInternalHttpBaseEndPoint(final String networkAlias)
103+
{
104+
return "http://" + networkAlias + ":" + OIDCServerContainer.PORT;
105+
}
106+
107+
public String getInternalHttpBaseEndPoint()
108+
{
109+
return getInternalHttpBaseEndPoint(this.getNetworkAlias());
110+
}
111+
112+
public String getExternalHttpBaseEndPoint()
113+
{
114+
return this.getContainer().getExternalHttpBaseEndPoint();
115+
}
116+
117+
@SuppressWarnings("PMD.UseTryWithResources") // Java 17 support
118+
public void warmUpWellKnownJWKsEndpoint()
119+
{
120+
final int slownessFactor = EnvironmentPerformance.cpuSlownessFactor();
121+
final HttpClient httpClient = HttpClient.newBuilder()
122+
.connectTimeout(Duration.ofSeconds(1L + slownessFactor))
123+
.build();
124+
125+
try
126+
{
127+
Unreliables.retryUntilSuccess(
128+
5 + slownessFactor,
129+
() -> httpClient.send(
130+
HttpRequest.newBuilder(URI.create(
131+
this.getExternalHttpBaseEndPoint() + "/.well-known/openid-configuration/jwks"))
132+
.timeout(Duration.ofSeconds(10L + slownessFactor * 5L))
133+
.GET()
134+
.build(),
135+
HttpResponse.BodyHandlers.discarding()));
136+
}
137+
finally
138+
{
139+
HttpClientCloser.close(httpClient);
140+
}
141+
}
142+
143+
public void addUser(
144+
final String email,
145+
final String name,
146+
final String pw)
147+
{
148+
this.apiAddUser(this.createDefaultBodyForAddUser(email, name, pw));
149+
}
150+
151+
protected void apiAddUser(final String jsonBody)
152+
{
153+
try(final CloseableHttpClient client = this.createDefaultHttpClient())
154+
{
155+
final HttpPost post = new HttpPost(this.getContainer().getExternalHttpBaseEndPoint() + "/api/v1/user");
156+
post.setEntity(new StringEntity(jsonBody));
157+
post.setHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.getMimeType());
158+
post.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType());
159+
160+
final ClassicHttpResponse response = client.execute(post, r -> r);
161+
if(response.getCode() != HttpStatus.SC_OK)
162+
{
163+
throw new IllegalStateException("Unable to create user; Expected statuscode 200 but got "
164+
+ response.getCode()
165+
+ "; Reason: " + response.getReasonPhrase());
166+
}
167+
}
168+
catch(final IOException ioe)
169+
{
170+
throw new UncheckedIOException(ioe);
171+
}
172+
}
173+
174+
protected String createDefaultBodyForAddUser(
175+
final String email,
176+
final String name,
177+
final String pw)
178+
{
179+
return """
180+
{
181+
"SubjectId":"%s",
182+
"Username":"%s",
183+
"Password":"%s",
184+
"Claims": [
185+
{
186+
"Type": "name",
187+
"Value": "%s",
188+
"ValueType": "string"
189+
},
190+
{
191+
"Type": "email",
192+
"Value": "%s",
193+
"ValueType": "string"
194+
}
195+
]
196+
}
197+
""".formatted(
198+
UUID.randomUUID().toString(),
199+
email,
200+
pw,
201+
name,
202+
email
203+
);
204+
}
205+
206+
protected CloseableHttpClient createDefaultHttpClient()
207+
{
208+
return HttpClientBuilder.create()
209+
.setConnectionManager(PoolingHttpClientConnectionManagerBuilder.create()
210+
.setDefaultConnectionConfig(ConnectionConfig.custom()
211+
.setConnectTimeout(Timeout.of(DEFAULT_TIMEOUT))
212+
.setSocketTimeout(Timeout.of(DEFAULT_TIMEOUT))
213+
.build())
214+
.build())
215+
.build();
216+
}
217+
218+
// region Configure
219+
220+
protected SELF withShouldAddDefaultUser(final boolean shouldAddDefaultUser)
221+
{
222+
this.shouldAddDefaultUser = shouldAddDefaultUser;
223+
return this.self();
224+
}
225+
226+
public SELF withDefaultUserEmail(final String defaultUserEmail)
227+
{
228+
this.defaultUserEmail = defaultUserEmail;
229+
return this.self();
230+
}
231+
232+
public SELF withDefaultUserName(final String defaultUserName)
233+
{
234+
this.defaultUserName = defaultUserName;
235+
return this.self();
236+
}
237+
238+
public SELF withDefaultUserPassword(final String defaultUserPassword)
239+
{
240+
this.defaultUserPassword = defaultUserPassword;
241+
return this.self();
242+
}
243+
244+
// endregion
245+
246+
@SuppressWarnings("unchecked")
247+
public SELF self()
248+
{
249+
return (SELF)this;
250+
}
251+
}

0 commit comments

Comments
 (0)