Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add @BasicAuthConfig for testing applications with Basic auth #158

Merged
merged 1 commit into from
Mar 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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