Skip to content

Commit

Permalink
Merge pull request #158 from aguibert/basic-auth
Browse files Browse the repository at this point in the history
Add @BasicAuthConfig for testing applications with Basic auth
  • Loading branch information
aguibert authored Mar 3, 2020
2 parents 408d702 + 25c34f2 commit 3796ed4
Show file tree
Hide file tree
Showing 12 changed files with 358 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2020 IBM Corporation and others
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.microshed.testing.jaxrs;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Used to annotate a REST Client to configure Basic Authorization
* that will be applied to all of its HTTP invocations.
* In order for this annotation to have any effect, the field must also
* be annotated with {@link RESTClient}.
*/
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface BasicAuthConfig {

String user();

String password();

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
*/
package org.microshed.testing.jaxrs;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -48,7 +50,9 @@ public class RestClientBuilder {
private String appContextRoot;
private String jaxrsPath;
private String jwt;
private String basicAuth;
private List<Class<?>> providers;
private final Map<String, String> headers = new HashMap<>();

/**
* @param appContextRoot The protocol, hostname, port, and application root path for the REST Client
Expand Down Expand Up @@ -81,7 +85,41 @@ public RestClientBuilder withJaxrsPath(String jaxrsPath) {
*/
public RestClientBuilder withJwt(String jwt) {
Objects.requireNonNull(jwt, "Supplied 'jwt' must not be null");
if (basicAuth != null)
throw new IllegalArgumentException("Cannot configure JWT and Basic Auth on the same REST client");
this.jwt = jwt;
headers.put("Authorization", "Bearer " + jwt);
LOGGER.debug("Using provided JWT auth header: " + jwt);
return this;
}

/**
* @param user The username portion of the Basic auth header
* @param password The password portion of the Basic auth header
* @return The same builder instance
*/
public RestClientBuilder withBasicAuth(String user, String password) {
Objects.requireNonNull(user, "Supplied 'user' must not be null");
Objects.requireNonNull(password, "Supplied 'password' must not be null");
if (jwt != null)
throw new IllegalArgumentException("Cannot configure JWT and Basic Auth on the same REST client");
String unEncoded = user + ":" + password;
this.basicAuth = Base64.getEncoder().encodeToString(unEncoded.getBytes(StandardCharsets.UTF_8));
headers.put("Authorization", "Basic " + basicAuth);
LOGGER.debug("Using provided Basic auth header: " + unEncoded + " --> " + basicAuth);
return this;
}

/**
* @return The same builder instance
*/
public RestClientBuilder withHeader(String key, String value) {
Objects.requireNonNull(key, "Supplied header 'key' must not be null");
Objects.requireNonNull(value, "Supplied header 'value' must not be null");
if (jwt != null)
throw new IllegalArgumentException("Cannot configure JWT and Basic Auth on the same REST client");
headers.put(key, value);
LOGGER.debug("Using provided header " + key + "=" + value);
return this;
}

Expand Down Expand Up @@ -111,12 +149,7 @@ public <T> T build(Class<T> clazz) {
bean.setResourceClass(clazz);
bean.setProviders(providers);
bean.setAddress(basePath);
if (jwt != null && jwt.length() > 0) {
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "Bearer " + jwt);
bean.setHeaders(headers);
LOGGER.debug("Using provided JWT auth header: " + jwt);
}
bean.setHeaders(headers);
return bean.create(clazz);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.junit.platform.commons.support.AnnotationSupport;
import org.microshed.testing.ApplicationEnvironment;
import org.microshed.testing.SharedContainerConfig;
import org.microshed.testing.jaxrs.BasicAuthConfig;
import org.microshed.testing.jaxrs.RESTClient;
import org.microshed.testing.jaxrs.RestClientBuilder;
import org.microshed.testing.jwt.JwtBuilder;
Expand Down Expand Up @@ -82,9 +83,23 @@ private static void injectRestClients(Class<?> clazz) {
throw new ExtensionConfigurationException("REST client field must be public, static, and non-final: " + restClientField);
}
RestClientBuilder rcBuilder = new RestClientBuilder();
String jwt = createJwtIfNeeded(restClientField);
if (jwt != null)
rcBuilder.withJwt(jwt);
JwtConfig jwtAnno = restClientField.getDeclaredAnnotation(JwtConfig.class);
BasicAuthConfig basicAnno = restClientField.getDeclaredAnnotation(BasicAuthConfig.class);
if (jwtAnno != null && basicAnno != null)
throw new ExtensionConfigurationException("Can only use one of @JwtConfig or @BasicAuthConfig on REST client field: " + restClientField);

if (jwtAnno != null) {
try {
String jwt = JwtBuilder.buildJwt(jwtAnno.subject(), jwtAnno.issuer(), jwtAnno.claims());
rcBuilder.withJwt(jwt);
} catch (Exception e) {
throw new ExtensionConfigurationException("Error while building JWT for field " + restClientField + " with JwtConfig: " + jwtAnno, e);
}
}
if (basicAnno != null) {
rcBuilder.withBasicAuth(basicAnno.user(), basicAnno.password());
}

Object restClient = rcBuilder.build(restClientField.getType());
try {
restClientField.set(null, restClient);
Expand All @@ -95,19 +110,6 @@ private static void injectRestClients(Class<?> clazz) {
}
}

private static String createJwtIfNeeded(Field restClientField) {
Field f = restClientField;
JwtConfig anno = f.getDeclaredAnnotation(JwtConfig.class);
if (anno != null) {
try {
return JwtBuilder.buildJwt(anno.subject(), anno.issuer(), anno.claims());
} catch (Exception e) {
throw new ExtensionConfigurationException("Error while building JWT for field " + f + " with JwtConfig: " + anno, e);
}
}
return null;
}

@SuppressWarnings({ "unchecked", "rawtypes" })
private static void configureRestAssured(ApplicationEnvironment config) {
if (!config.configureRestAssured())
Expand Down
6 changes: 5 additions & 1 deletion core/src/main/java/org/microshed/testing/jwt/JwtConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,13 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.microshed.testing.jaxrs.RESTClient;

/**
* Used to annotate a REST Client to configure MicroProfile JWT settings
* that will be applied to all of its HTTP invocations
* that will be applied to all of its HTTP invocations.
* In order for this annotation to have any effect, the field must also
* be annotated with {@link RESTClient}.
*/
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
Expand Down
3 changes: 2 additions & 1 deletion docs/features/Examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ Sometimes code is worth a thousand words. Here are some pointers to working exam
- [Basic JAX-RS application using Gradle](https://github.com/MicroShed/microshed-testing/tree/master/sample-apps/jaxrs-json)
- [Basic JAX-RS application using Maven](https://github.com/MicroShed/microshed-testing/tree/master/sample-apps/maven-app)
- [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)
- [JAX-RS and JDBC applicaiton using a PostgreSQL database](https://github.com/MicroShed/microshed-testing/tree/master/sample-apps/jdbc-app)
- [JAX-RS and JDBC applicaiton using a PostgreSQL database](https://github.com/MicroShed/microshed-testing/tree/master/sample-apps/jdbc-app)
- [JAX-RS application secured with Basic Auth](https://github.com/MicroShed/microshed-testing/tree/master/sample-apps/jaxrs-basicauth)
- [JAX-RS application secured with MP JWT](https://github.com/MicroShed/microshed-testing/tree/master/sample-apps/jaxrs-mpjwt)
- [JAX-RS and MongoDB application that depends on an external REST service](https://github.com/MicroShed/microshed-testing/tree/master/sample-apps/everything-app)
- [Application with no Dockerfile using OpenLiberty adapter](https://github.com/MicroShed/microshed-testing/tree/master/sample-apps/liberty-app)
Expand Down
17 changes: 17 additions & 0 deletions sample-apps/jaxrs-basicauth/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# OpenLiberty
FROM openliberty/open-liberty:full-java8-openj9-ubi
COPY src/main/liberty/config /config/
ADD build/libs/myservice.war /config/apps

# Wildfly
#FROM jboss/wildfly
#ADD build/libs/myservice.war /opt/jboss/wildfly/standalone/deployments/

# Payara
#FROM payara/micro:5.193
#CMD ["--deploymentDir", "/opt/payara/deployments", "--noCluster"]
#ADD build/libs/myservice.war /opt/payara/deployments

# TomEE
#FROM tomee:8-jre-8.0.0-M2-microprofile
#COPY build/libs/myservice.war /usr/local/tomee/webapps/
19 changes: 19 additions & 0 deletions sample-apps/jaxrs-basicauth/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
plugins {
id 'war'
}

dependencies {
providedCompile 'javax:javaee-api:8.0.1'
providedCompile 'org.eclipse.microprofile:microprofile:2.1'
testCompile project(':microshed-testing-testcontainers')
testCompile group: 'org.assertj', name: 'assertj-core', version: '3.15.0'
testCompile group: 'org.slf4j', name: 'slf4j-log4j12', version: '1.7.29'
testImplementation 'org.junit.jupiter:junit-jupiter:5.6.0'
}

war.archiveName 'myservice.war'
test.dependsOn 'war'

// Always re-run tests on every build for the sake of this sample
// In a real project, this setting would not be desirable
test.outputs.upToDateWhen { false }
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (c) 2019 IBM Corporation and others
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.example.app;

import javax.annotation.security.PermitAll;
import javax.annotation.security.RolesAllowed;
import javax.enterprise.context.RequestScoped;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.SecurityContext;

@Path("/data")
@RequestScoped
@RolesAllowed("admin")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class SecuredService {

@Context
SecurityContext securityContext;

@Context
HttpHeaders headers;

@GET
@Path("/ping")
@PermitAll
public String ping() {
return "ping";
}

@GET
@Path("/headers")
@PermitAll
public String getHeaders() {
String result = "*** HEADERS: " + headers.getRequestHeaders().toString();
result += "\n" + "*** PRINCIPAL NAME=" + ( securityContext == null ? "null" : securityContext.getUserPrincipal().getName());
return result;
}

@GET
public String getSecuredInfo() {
return "this is some secured info";
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2019 IBM Corporation and others
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* You may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.example.app;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/app")
public class SecuredServiceApp extends Application { }
27 changes: 27 additions & 0 deletions sample-apps/jaxrs-basicauth/src/main/liberty/config/server.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<server>

<featureManager>
<feature>jaxrs-2.1</feature>
<feature>jsonb-1.0</feature>
<feature>mpHealth-1.0</feature>
<feature>mpConfig-1.3</feature>
<feature>mpRestClient-1.1</feature>
<feature>cdi-2.0</feature>
<feature>appSecurity-3.0</feature>
</featureManager>

<basicRegistry id="basic">
<user name="alice" password="alicepwd"/>
<user name="bob" password="bobpwd"/>
</basicRegistry>

<webApplication location="myservice.war">
<application-bnd>
<!-- this can also be defined in web.xml instead -->
<security-role name="admin">
<user name="bob"/>
</security-role>
</application-bnd>
</webApplication>

</server>
Loading

0 comments on commit 3796ed4

Please sign in to comment.