Skip to content

Commit 3796ed4

Browse files
authored
Merge pull request #158 from aguibert/basic-auth
Add @BasicAuthConfig for testing applications with Basic auth
2 parents 408d702 + 25c34f2 commit 3796ed4

File tree

12 files changed

+358
-24
lines changed

12 files changed

+358
-24
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright (c) 2020 IBM Corporation and others
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* You may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
package org.microshed.testing.jaxrs;
20+
21+
import java.lang.annotation.ElementType;
22+
import java.lang.annotation.Retention;
23+
import java.lang.annotation.RetentionPolicy;
24+
import java.lang.annotation.Target;
25+
26+
/**
27+
* Used to annotate a REST Client to configure Basic Authorization
28+
* that will be applied to all of its HTTP invocations.
29+
* In order for this annotation to have any effect, the field must also
30+
* be annotated with {@link RESTClient}.
31+
*/
32+
@Target({ ElementType.FIELD })
33+
@Retention(RetentionPolicy.RUNTIME)
34+
public @interface BasicAuthConfig {
35+
36+
String user();
37+
38+
String password();
39+
40+
}

core/src/main/java/org/microshed/testing/jaxrs/RestClientBuilder.java

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
*/
1919
package org.microshed.testing.jaxrs;
2020

21+
import java.nio.charset.StandardCharsets;
2122
import java.util.Arrays;
23+
import java.util.Base64;
2224
import java.util.Collections;
2325
import java.util.HashMap;
2426
import java.util.List;
@@ -48,7 +50,9 @@ public class RestClientBuilder {
4850
private String appContextRoot;
4951
private String jaxrsPath;
5052
private String jwt;
53+
private String basicAuth;
5154
private List<Class<?>> providers;
55+
private final Map<String, String> headers = new HashMap<>();
5256

5357
/**
5458
* @param appContextRoot The protocol, hostname, port, and application root path for the REST Client
@@ -81,7 +85,41 @@ public RestClientBuilder withJaxrsPath(String jaxrsPath) {
8185
*/
8286
public RestClientBuilder withJwt(String jwt) {
8387
Objects.requireNonNull(jwt, "Supplied 'jwt' must not be null");
88+
if (basicAuth != null)
89+
throw new IllegalArgumentException("Cannot configure JWT and Basic Auth on the same REST client");
8490
this.jwt = jwt;
91+
headers.put("Authorization", "Bearer " + jwt);
92+
LOGGER.debug("Using provided JWT auth header: " + jwt);
93+
return this;
94+
}
95+
96+
/**
97+
* @param user The username portion of the Basic auth header
98+
* @param password The password portion of the Basic auth header
99+
* @return The same builder instance
100+
*/
101+
public RestClientBuilder withBasicAuth(String user, String password) {
102+
Objects.requireNonNull(user, "Supplied 'user' must not be null");
103+
Objects.requireNonNull(password, "Supplied 'password' must not be null");
104+
if (jwt != null)
105+
throw new IllegalArgumentException("Cannot configure JWT and Basic Auth on the same REST client");
106+
String unEncoded = user + ":" + password;
107+
this.basicAuth = Base64.getEncoder().encodeToString(unEncoded.getBytes(StandardCharsets.UTF_8));
108+
headers.put("Authorization", "Basic " + basicAuth);
109+
LOGGER.debug("Using provided Basic auth header: " + unEncoded + " --> " + basicAuth);
110+
return this;
111+
}
112+
113+
/**
114+
* @return The same builder instance
115+
*/
116+
public RestClientBuilder withHeader(String key, String value) {
117+
Objects.requireNonNull(key, "Supplied header 'key' must not be null");
118+
Objects.requireNonNull(value, "Supplied header 'value' must not be null");
119+
if (jwt != null)
120+
throw new IllegalArgumentException("Cannot configure JWT and Basic Auth on the same REST client");
121+
headers.put(key, value);
122+
LOGGER.debug("Using provided header " + key + "=" + value);
85123
return this;
86124
}
87125

@@ -111,12 +149,7 @@ public <T> T build(Class<T> clazz) {
111149
bean.setResourceClass(clazz);
112150
bean.setProviders(providers);
113151
bean.setAddress(basePath);
114-
if (jwt != null && jwt.length() > 0) {
115-
Map<String, String> headers = new HashMap<>();
116-
headers.put("Authorization", "Bearer " + jwt);
117-
bean.setHeaders(headers);
118-
LOGGER.debug("Using provided JWT auth header: " + jwt);
119-
}
152+
bean.setHeaders(headers);
120153
return bean.create(clazz);
121154
}
122155

core/src/main/java/org/microshed/testing/jupiter/MicroShedTestExtension.java

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.junit.platform.commons.support.AnnotationSupport;
3333
import org.microshed.testing.ApplicationEnvironment;
3434
import org.microshed.testing.SharedContainerConfig;
35+
import org.microshed.testing.jaxrs.BasicAuthConfig;
3536
import org.microshed.testing.jaxrs.RESTClient;
3637
import org.microshed.testing.jaxrs.RestClientBuilder;
3738
import org.microshed.testing.jwt.JwtBuilder;
@@ -82,9 +83,23 @@ private static void injectRestClients(Class<?> clazz) {
8283
throw new ExtensionConfigurationException("REST client field must be public, static, and non-final: " + restClientField);
8384
}
8485
RestClientBuilder rcBuilder = new RestClientBuilder();
85-
String jwt = createJwtIfNeeded(restClientField);
86-
if (jwt != null)
87-
rcBuilder.withJwt(jwt);
86+
JwtConfig jwtAnno = restClientField.getDeclaredAnnotation(JwtConfig.class);
87+
BasicAuthConfig basicAnno = restClientField.getDeclaredAnnotation(BasicAuthConfig.class);
88+
if (jwtAnno != null && basicAnno != null)
89+
throw new ExtensionConfigurationException("Can only use one of @JwtConfig or @BasicAuthConfig on REST client field: " + restClientField);
90+
91+
if (jwtAnno != null) {
92+
try {
93+
String jwt = JwtBuilder.buildJwt(jwtAnno.subject(), jwtAnno.issuer(), jwtAnno.claims());
94+
rcBuilder.withJwt(jwt);
95+
} catch (Exception e) {
96+
throw new ExtensionConfigurationException("Error while building JWT for field " + restClientField + " with JwtConfig: " + jwtAnno, e);
97+
}
98+
}
99+
if (basicAnno != null) {
100+
rcBuilder.withBasicAuth(basicAnno.user(), basicAnno.password());
101+
}
102+
88103
Object restClient = rcBuilder.build(restClientField.getType());
89104
try {
90105
restClientField.set(null, restClient);
@@ -95,19 +110,6 @@ private static void injectRestClients(Class<?> clazz) {
95110
}
96111
}
97112

98-
private static String createJwtIfNeeded(Field restClientField) {
99-
Field f = restClientField;
100-
JwtConfig anno = f.getDeclaredAnnotation(JwtConfig.class);
101-
if (anno != null) {
102-
try {
103-
return JwtBuilder.buildJwt(anno.subject(), anno.issuer(), anno.claims());
104-
} catch (Exception e) {
105-
throw new ExtensionConfigurationException("Error while building JWT for field " + f + " with JwtConfig: " + anno, e);
106-
}
107-
}
108-
return null;
109-
}
110-
111113
@SuppressWarnings({ "unchecked", "rawtypes" })
112114
private static void configureRestAssured(ApplicationEnvironment config) {
113115
if (!config.configureRestAssured())

core/src/main/java/org/microshed/testing/jwt/JwtConfig.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,13 @@
2323
import java.lang.annotation.RetentionPolicy;
2424
import java.lang.annotation.Target;
2525

26+
import org.microshed.testing.jaxrs.RESTClient;
27+
2628
/**
2729
* Used to annotate a REST Client to configure MicroProfile JWT settings
28-
* that will be applied to all of its HTTP invocations
30+
* that will be applied to all of its HTTP invocations.
31+
* In order for this annotation to have any effect, the field must also
32+
* be annotated with {@link RESTClient}.
2933
*/
3034
@Target({ ElementType.FIELD })
3135
@Retention(RetentionPolicy.RUNTIME)

docs/features/Examples.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ Sometimes code is worth a thousand words. Here are some pointers to working exam
1111
- [Basic JAX-RS application using Gradle](https://github.com/MicroShed/microshed-testing/tree/master/sample-apps/jaxrs-json)
1212
- [Basic JAX-RS application using Maven](https://github.com/MicroShed/microshed-testing/tree/master/sample-apps/maven-app)
1313
- [Basic JAX-RS application using REST Assured](https://github.com/MicroShed/microshed-testing/blob/master/sample-apps/everything-app/src/test/java/org/example/app/RestAssuredTest.java)
14-
- [JAX-RS and JDBC applicaiton using a PostgreSQL database](https://github.com/MicroShed/microshed-testing/tree/master/sample-apps/jdbc-app)
14+
- [JAX-RS and JDBC applicaiton using a PostgreSQL database](https://github.com/MicroShed/microshed-testing/tree/master/sample-apps/jdbc-app)
15+
- [JAX-RS application secured with Basic Auth](https://github.com/MicroShed/microshed-testing/tree/master/sample-apps/jaxrs-basicauth)
1516
- [JAX-RS application secured with MP JWT](https://github.com/MicroShed/microshed-testing/tree/master/sample-apps/jaxrs-mpjwt)
1617
- [JAX-RS and MongoDB application that depends on an external REST service](https://github.com/MicroShed/microshed-testing/tree/master/sample-apps/everything-app)
1718
- [Application with no Dockerfile using OpenLiberty adapter](https://github.com/MicroShed/microshed-testing/tree/master/sample-apps/liberty-app)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# OpenLiberty
2+
FROM openliberty/open-liberty:full-java8-openj9-ubi
3+
COPY src/main/liberty/config /config/
4+
ADD build/libs/myservice.war /config/apps
5+
6+
# Wildfly
7+
#FROM jboss/wildfly
8+
#ADD build/libs/myservice.war /opt/jboss/wildfly/standalone/deployments/
9+
10+
# Payara
11+
#FROM payara/micro:5.193
12+
#CMD ["--deploymentDir", "/opt/payara/deployments", "--noCluster"]
13+
#ADD build/libs/myservice.war /opt/payara/deployments
14+
15+
# TomEE
16+
#FROM tomee:8-jre-8.0.0-M2-microprofile
17+
#COPY build/libs/myservice.war /usr/local/tomee/webapps/
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
plugins {
2+
id 'war'
3+
}
4+
5+
dependencies {
6+
providedCompile 'javax:javaee-api:8.0.1'
7+
providedCompile 'org.eclipse.microprofile:microprofile:2.1'
8+
testCompile project(':microshed-testing-testcontainers')
9+
testCompile group: 'org.assertj', name: 'assertj-core', version: '3.15.0'
10+
testCompile group: 'org.slf4j', name: 'slf4j-log4j12', version: '1.7.29'
11+
testImplementation 'org.junit.jupiter:junit-jupiter:5.6.0'
12+
}
13+
14+
war.archiveName 'myservice.war'
15+
test.dependsOn 'war'
16+
17+
// Always re-run tests on every build for the sake of this sample
18+
// In a real project, this setting would not be desirable
19+
test.outputs.upToDateWhen { false }
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright (c) 2019 IBM Corporation and others
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* You may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
package org.example.app;
20+
21+
import javax.annotation.security.PermitAll;
22+
import javax.annotation.security.RolesAllowed;
23+
import javax.enterprise.context.RequestScoped;
24+
import javax.ws.rs.Consumes;
25+
import javax.ws.rs.GET;
26+
import javax.ws.rs.Path;
27+
import javax.ws.rs.Produces;
28+
import javax.ws.rs.core.Context;
29+
import javax.ws.rs.core.HttpHeaders;
30+
import javax.ws.rs.core.MediaType;
31+
import javax.ws.rs.core.SecurityContext;
32+
33+
@Path("/data")
34+
@RequestScoped
35+
@RolesAllowed("admin")
36+
@Produces(MediaType.APPLICATION_JSON)
37+
@Consumes(MediaType.APPLICATION_JSON)
38+
public class SecuredService {
39+
40+
@Context
41+
SecurityContext securityContext;
42+
43+
@Context
44+
HttpHeaders headers;
45+
46+
@GET
47+
@Path("/ping")
48+
@PermitAll
49+
public String ping() {
50+
return "ping";
51+
}
52+
53+
@GET
54+
@Path("/headers")
55+
@PermitAll
56+
public String getHeaders() {
57+
String result = "*** HEADERS: " + headers.getRequestHeaders().toString();
58+
result += "\n" + "*** PRINCIPAL NAME=" + ( securityContext == null ? "null" : securityContext.getUserPrincipal().getName());
59+
return result;
60+
}
61+
62+
@GET
63+
public String getSecuredInfo() {
64+
return "this is some secured info";
65+
}
66+
67+
68+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright (c) 2019 IBM Corporation and others
3+
*
4+
* See the NOTICE file(s) distributed with this work for additional
5+
* information regarding copyright ownership.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* You may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*/
19+
package org.example.app;
20+
21+
import javax.ws.rs.ApplicationPath;
22+
import javax.ws.rs.core.Application;
23+
24+
@ApplicationPath("/app")
25+
public class SecuredServiceApp extends Application { }
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<server>
2+
3+
<featureManager>
4+
<feature>jaxrs-2.1</feature>
5+
<feature>jsonb-1.0</feature>
6+
<feature>mpHealth-1.0</feature>
7+
<feature>mpConfig-1.3</feature>
8+
<feature>mpRestClient-1.1</feature>
9+
<feature>cdi-2.0</feature>
10+
<feature>appSecurity-3.0</feature>
11+
</featureManager>
12+
13+
<basicRegistry id="basic">
14+
<user name="alice" password="alicepwd"/>
15+
<user name="bob" password="bobpwd"/>
16+
</basicRegistry>
17+
18+
<webApplication location="myservice.war">
19+
<application-bnd>
20+
<!-- this can also be defined in web.xml instead -->
21+
<security-role name="admin">
22+
<user name="bob"/>
23+
</security-role>
24+
</application-bnd>
25+
</webApplication>
26+
27+
</server>

0 commit comments

Comments
 (0)