This is an automated email from the ASF dual-hosted git repository. jlmonteiro pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/tomee.git
The following commit(s) were added to refs/heads/master by this push: new e77be55 TOMEE-3758 Jakarta Security example with tomcat-users.xml identity store new 57ada73 Merge branch 'master' of github.com:apache/tomee into master e77be55 is described below commit e77be5540121c2fa9a7925828d8bee6dcff1019f Author: Jean-Louis Monteiro <jlmonte...@tomitribe.com> AuthorDate: Thu Jun 17 00:08:32 2021 +0200 TOMEE-3758 Jakarta Security example with tomcat-users.xml identity store --- examples/pom.xml | 4 + .../security-tomcat-user-identitystore/README.adoc | 172 +++++++++++++++++++++ .../security-tomcat-user-identitystore/pom.xml | 74 +++++++++ .../src/main/java/org/superbiz/movie/Api.java | 26 ++++ .../src/main/java/org/superbiz/movie}/Movie.java | 12 +- .../org/superbiz/movie/MovieAdminResource.java | 76 +++++++++ .../java/org/superbiz/movie/MovieResource.java | 69 +++++++++ .../main/java/org/superbiz/movie/MovieStore.java | 63 ++++++++ .../src/main/resources/META-INF/beans.xml | 18 +++ .../src/main/resources/conf/tomcat-users.xml | 24 +++ .../src/main/webapp/WEB-INF/web.xml | 35 +++++ .../java/org/superbiz/movie/BasicAuthFilter.java | 41 +++++ .../java/org/superbiz/movie/MovieResourceTest.java | 170 ++++++++++++++++++++ 13 files changed, 783 insertions(+), 1 deletion(-) diff --git a/examples/pom.xml b/examples/pom.xml index c4a833a..6c7b962 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -216,6 +216,10 @@ <module>cloud-tomee-azure</module> <module>mp-faulttolerance-timeout</module> <module>xa-datasource</module> + + <!-- Jakarta Security Examples --> + <module>security-tomcat-user-identitystore</module> + </modules> <dependencies> <dependency> diff --git a/examples/security-tomcat-user-identitystore/README.adoc b/examples/security-tomcat-user-identitystore/README.adoc new file mode 100644 index 0000000..2e8c3de --- /dev/null +++ b/examples/security-tomcat-user-identitystore/README.adoc @@ -0,0 +1,172 @@ +:index-group: Jakarta Security +:jbake-type: page +:jbake-status: status=published += Jakarta Security with Tomcat tomcat-users.xml identity store + +TomEE has its own independent Jakarta Security implementation https://eclipse-ee4j.github.io/security-api/ . + +[NOTE] +==== +Jakarta Security defines a standard for creating secure Jakarta EE applications in modern application paradigms. It defines an overarching (end-user targeted) Security API for Jakarta EE Applications. + +Jakarta Security builds on the lower level Security SPIs defined by Jakarta Authentication and Jakarta Authorization, which are both not end-end targeted. +==== + +This example focuses in showing how to leverage Jakarta Security in TomEE with Tomcat's tomcat-users.xml. +TomEE out of the box supports it as an identity store. + +== Implement a simple JAX-RS application + +This movie example has 2 resources, one of them `MovieAdminResource` is a protected resource to ensure only admin users can add or delete movies. + +[source,xml] +---- +<web-app + xmlns="http://xmlns.jcp.org/xml/ns/javaee" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" + version="3.1" +> + + <!-- Security constraints --> + + <security-constraint> + <web-resource-collection> + <web-resource-name>Protected admin resource/url</web-resource-name> + <url-pattern>/api/movies/*</url-pattern> + <http-method-omission>GET</http-method-omission> + </web-resource-collection> + <auth-constraint> + <role-name>admin</role-name> + </auth-constraint> + </security-constraint> + +</web-app> +---- + +== Defining identity store and authentication mechanism + +Jakarta Security requires 2 things to authenticate a user + +* the identity store (aka `tomcat-users.xml` in this case): this is basically where users are stored with their user +name, password, and the roles +* the authentication mechanism: how the credentials are passed in. + +In this example, we want to use `tomcat-users.xml` identity store and basic authentication. +We can define that in the resource itself using 2 annotations + +[source,java] +---- +@Path("/movies") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@TomcatUserIdentityStoreDefinition +@BasicAuthenticationMechanismDefinition +@ApplicationScoped +public class MovieAdminResource { + + private static final Logger LOGGER = Logger.getLogger(MovieAdminResource.class.getName()); + + @Inject + private MovieStore store; + + // JAXRS security context also wired with Jakarta Security + @Context + private javax.ws.rs.core.SecurityContext securityContext; + + @POST + public Movie addMovie(final Movie newMovie) { + LOGGER.info(getUserName() + " adding new movie " + newMovie); + return store.addMovie(newMovie); + } + + // See source file for full content + + private String getUserName() { + if (securityContext.getUserPrincipal() != null) { + return String.format("%s[admin=%s]", + securityContext.getUserPrincipal().getName(), + securityContext.isUserInRole("admin")); + } + + return null; + } + +} +---- + +IMPORTANT: +==== +In TomEE, Jakarta Security is wired in all layers, you can use + +* `javax.ws.rs.core.SecurityContext#getUserPrincipal` and `isUserInRole` to get the User Principal and check if the user has a given role +* `javax.security.enterprise.SecurityContext#getCallerPrincipal` and `isCallerInRole` to get the Caller Principal (notice the difference in terms of naming) and check if a caller has a given role +* `javax.servlet.http.HttpServletRequest#getUserPrincipal` and `isUserInRole` +* `javax.ejb.SessionContext#getCallerPrincipal` and `isCallerInRole` +* the `Subject` from the `PolicyContext` but this is less used +==== + +A lot of different APIs to retrieve the principal and check whereas it has a given role. +It's all wired in and consistent in TomEE. No special configuration is needed. + +Finally, `MovieResource` does not require any authentication or user permissions, but for logging purposes in this test, it will use the Jakarta Security `SecurityContext` to grab the caller principal and do some role checks. + +== Add users to the regular `tomcat-users.xml` + +The file location is by default `${catalina.base}/conf`. +The file can be located anywhere. +If you are not using the default location, make sure to update the `server.xml` accordingly. + +[source,xml] +---- +<tomcat-users> + <user name="tomcat" password="tomcat" roles="tomcat"/> + <user name="user" password="user" roles="user"/> + + <user name="tom" password="secret1" roles="admin,manager"/> + <user name="emma" password="secret2" roles="admin,employee"/> + <user name="bob" password="secret3" roles="admin"/> +</tomcat-users> +---- + +== Running + +Were we to run the above Main class or Test Case we'd see output like the following: + +[source,bash] +---- +INFOS: Service URI: http://localhost:56147/api/movies -> Pojo org.superbiz.movie.MovieAdminResource +juin 15, 2021 3:48:32 PM org.apache.openejb.server.cxf.rs.CxfRsHttpListener logEndpoints +INFOS: DELETE http://localhost:56147/api/movies/{id} -> Movie deleteMovie(int) +juin 15, 2021 3:48:32 PM org.apache.openejb.server.cxf.rs.CxfRsHttpListener logEndpoints +INFOS: POST http://localhost:56147/api/movies -> Movie addMovie(Movie) +juin 15, 2021 3:48:32 PM org.apache.openejb.server.cxf.rs.CxfRsHttpListener logEndpoints +INFOS: Service URI: http://localhost:56147/api/movies -> Pojo org.superbiz.movie.MovieResource +juin 15, 2021 3:48:32 PM org.apache.openejb.server.cxf.rs.CxfRsHttpListener logEndpoints +INFOS: GET http://localhost:56147/api/movies -> List<Movie> getAllMovies() +juin 15, 2021 3:48:32 PM org.apache.openejb.server.cxf.rs.CxfRsHttpListener logEndpoints +INFOS: GET http://localhost:56147/api/movies/{id} -> Movie getMovie(int) +juin 15, 2021 3:48:32 PM org.apache.openejb.server.cxf.rs.CxfRsHttpListener logEndpoints +INFOS: Service URI: http://localhost:56147/api/openapi -> Pojo org.apache.geronimo.microprofile.openapi.jaxrs.OpenAPIEndpoint +juin 15, 2021 3:48:32 PM org.apache.openejb.server.cxf.rs.CxfRsHttpListener logEndpoints +INFOS: GET http://localhost:56147/api/openapi -> OpenAPI get() +juin 15, 2021 3:48:32 PM sun.reflect.DelegatingMethodAccessorImpl invoke +INFOS: Deployment of web application directory [/private/var/folders/03/fjcmr3cs2rnbtfcqd9w1nntc0000gn/T/temp2373416631427015263dir/apache-tomee/webapps/ROOT] has finished in [15,655] ms +juin 15, 2021 3:48:32 PM sun.reflect.DelegatingMethodAccessorImpl invoke +INFOS: Starting ProtocolHandler ["http-nio-56147"] +juin 15, 2021 3:48:32 PM sun.reflect.DelegatingMethodAccessorImpl invoke +INFOS: Server startup in [15904] milliseconds +juin 15, 2021 3:48:32 PM sun.reflect.DelegatingMethodAccessorImpl invoke +INFOS: Full bootstrap in [22621] milliseconds +juin 15, 2021 3:48:33 PM org.superbiz.movie.MovieAdminResource addMovie +INFOS: tom[admin=true] adding new movie Movie{title='Shanghai Noon', director='Tom Dey', genre='Comedy', id=7, year=2000} +juin 15, 2021 3:48:34 PM org.superbiz.movie.MovieResource getAllMovies +INFOS: tomcat[admin=false] reading movies +juin 15, 2021 3:48:34 PM org.superbiz.movie.MovieResource getAllMovies +INFOS: null reading movies +juin 15, 2021 3:48:34 PM org.superbiz.movie.MovieResource getAllMovies +INFOS: emma[admin=true] reading movies +juin 15, 2021 3:48:34 PM org.superbiz.movie.MovieResource getMovie +INFOS: bob[admin=true] reading movie 2 / Movie{title='Starsky & Hutch', director='Todd Phillips', genre='Action', id=2, year=2004} + +---- diff --git a/examples/security-tomcat-user-identitystore/pom.xml b/examples/security-tomcat-user-identitystore/pom.xml new file mode 100644 index 0000000..4206f47 --- /dev/null +++ b/examples/security-tomcat-user-identitystore/pom.xml @@ -0,0 +1,74 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.superbiz</groupId> + <artifactId>serverless-tomee-microprofile</artifactId> + <version>8.0.8-SNAPSHOT</version> + + <name>TomEE :: Examples :: Jakarta Security tomcat-user.xml Identity Store</name> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <version.tomee>8.0.8-SNAPSHOT</version.tomee> + </properties> + <dependencies> + <dependency> + <groupId>org.apache.tomee.bom</groupId> + <artifactId>tomee-microprofile</artifactId> + <version>${version.tomee}</version> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.13.1</version> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.5.1</version> + <configuration> + <source>1.8</source> + <target>1.8</target> + </configuration> + </plugin> + </plugins> + </build> + + <!-- + This section allows you to configure where to publish libraries for sharing. + It is not required and may be deleted. For more information see: + http://maven.apache.org/plugins/maven-deploy-plugin/ + --> + <distributionManagement> + <repository> + <id>localhost</id> + <url>file://${basedir}/target/repo/</url> + </repository> + <snapshotRepository> + <id>localhost</id> + <url>file://${basedir}/target/snapshot-repo/</url> + </snapshotRepository> + </distributionManagement> +</project> diff --git a/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/Api.java b/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/Api.java new file mode 100644 index 0000000..6826022 --- /dev/null +++ b/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/Api.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.superbiz.movie; + +import javax.annotation.security.DeclareRoles; +import javax.ws.rs.ApplicationPath; +import javax.ws.rs.core.Application; + +@ApplicationPath("/api") +@DeclareRoles({"admin"}) +public class Api extends Application { +} diff --git a/examples/serverless-tomee-microprofile/src/main/java/org/superbiz/movie/wp/Movie.java b/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/Movie.java similarity index 85% rename from examples/serverless-tomee-microprofile/src/main/java/org/superbiz/movie/wp/Movie.java rename to examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/Movie.java index b5b02df..774c306 100644 --- a/examples/serverless-tomee-microprofile/src/main/java/org/superbiz/movie/wp/Movie.java +++ b/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/Movie.java @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.superbiz.movie.wp; +package org.superbiz.movie; public class Movie { @@ -74,4 +74,14 @@ public class Movie { public void setYear(final int year) { this.year = year; } + + @Override public String toString() { + return "Movie{" + + "title='" + title + '\'' + + ", director='" + director + '\'' + + ", genre='" + genre + '\'' + + ", id=" + id + + ", year=" + year + + '}'; + } } diff --git a/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/MovieAdminResource.java b/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/MovieAdminResource.java new file mode 100644 index 0000000..cbe2f24 --- /dev/null +++ b/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/MovieAdminResource.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.superbiz.movie; + +import org.apache.tomee.security.cdi.TomcatUserIdentityStoreDefinition; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.security.enterprise.authentication.mechanism.http.BasicAuthenticationMechanismDefinition; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.SecurityContext; +import java.util.logging.Logger; + +@Path("/movies") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@TomcatUserIdentityStoreDefinition +@BasicAuthenticationMechanismDefinition +@ApplicationScoped +public class MovieAdminResource { + + private static final Logger LOGGER = Logger.getLogger(MovieAdminResource.class.getName()); + + @Inject + private MovieStore store; + + // JAXRS security context also wired + @Context + private SecurityContext securityContext; + + @POST + public Movie addMovie(final Movie newMovie) { + LOGGER.info(getUserName() + " adding new movie " + newMovie); + return store.addMovie(newMovie); + } + + @DELETE + @Path("{id}") + public Movie deleteMovie(@PathParam("id") final int id) { + final Movie movie = store.deleteMovie(id); + LOGGER.info(getUserName() + " deleting movie " + id + " / " + movie); + return movie; + } + + private String getUserName() { + if (securityContext.getUserPrincipal() != null) { + return String.format("%s[admin=%s]", + securityContext.getUserPrincipal().getName(), + securityContext.isUserInRole("admin")); + } + + return null; + } + +} diff --git a/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/MovieResource.java b/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/MovieResource.java new file mode 100644 index 0000000..73793f0 --- /dev/null +++ b/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/MovieResource.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.superbiz.movie; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; +import javax.security.enterprise.SecurityContext; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import java.util.List; +import java.util.logging.Logger; + +@Path("/movies") +@Produces(MediaType.APPLICATION_JSON) +@Consumes(MediaType.APPLICATION_JSON) +@ApplicationScoped +public class MovieResource { + + private static final Logger LOGGER = Logger.getLogger(MovieResource.class.getName()); + + @Inject + private MovieStore store; + + // jakarta enterprise security context + @Inject + private SecurityContext securityContext; + + @GET + public List<Movie> getAllMovies() { + LOGGER.info(getCallerName() + " reading movies"); + return store.getAllMovies(); + } + + @GET + @Path("{id}") + public Movie getMovie(@PathParam("id") final int id) { + final Movie movie = store.getMovie(id); + LOGGER.info(getCallerName() + " reading movie " + id + " / " + movie); + return movie; + } + + private String getCallerName() { + if (securityContext.getCallerPrincipal() != null) { + return String.format("%s[admin=%s]", + securityContext.getCallerPrincipal().getName(), + securityContext.isCallerInRole("admin")); + } + + return null; + } +} diff --git a/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/MovieStore.java b/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/MovieStore.java new file mode 100644 index 0000000..5430474 --- /dev/null +++ b/examples/security-tomcat-user-identitystore/src/main/java/org/superbiz/movie/MovieStore.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.superbiz.movie; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.RequestScoped; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +// request scoped is totally desired in this simple test +// the goal is for each request we make to have always the same piece of data +// it makes the assertions easier and not dependant on the order of the execution +@RequestScoped +public class MovieStore { + + // not really required to have a concurrent map because it's request scoped bean + private final ConcurrentMap<Integer, Movie> store = new ConcurrentHashMap<>(); + + @PostConstruct + public void construct(){ + this.addMovie(new Movie("Wedding Crashers", "David Dobkin", "Comedy", 1, 2005)); + this.addMovie(new Movie("Starsky & Hutch", "Todd Phillips", "Action", 2, 2004)); + this.addMovie(new Movie("Shanghai Knights", "David Dobkin", "Action", 3, 2003)); + this.addMovie(new Movie("I-Spy", "Betty Thomas", "Adventure", 4, 2002)); + this.addMovie(new Movie("The Royal Tenenbaums", "Wes Anderson", "Comedy", 5, 2001)); + this.addMovie(new Movie("Zoolander", "Ben Stiller", "Comedy", 6, 2001)); + } + + public List<Movie> getAllMovies() { + return new ArrayList<>(store.values()); + } + + public Movie addMovie(final Movie newMovie) { + store.putIfAbsent(newMovie.getId(), newMovie); + return newMovie; + } + + public Movie deleteMovie(final int id) { + return store.remove(id); + } + + public Movie getMovie(final int id) { + return store.get(id); + } + +} diff --git a/examples/security-tomcat-user-identitystore/src/main/resources/META-INF/beans.xml b/examples/security-tomcat-user-identitystore/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000..b240367 --- /dev/null +++ b/examples/security-tomcat-user-identitystore/src/main/resources/META-INF/beans.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You 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. +--> +<beans/> diff --git a/examples/security-tomcat-user-identitystore/src/main/resources/conf/tomcat-users.xml b/examples/security-tomcat-user-identitystore/src/main/resources/conf/tomcat-users.xml new file mode 100644 index 0000000..eaf108a --- /dev/null +++ b/examples/security-tomcat-user-identitystore/src/main/resources/conf/tomcat-users.xml @@ -0,0 +1,24 @@ +<!-- + ~ Licensed to the Apache Software Foundation (ASF) under one or more + ~ contributor license agreements. See the NOTICE file distributed with + ~ this work for additional information regarding copyright ownership. + ~ The ASF licenses this file to You 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. + --> +<tomcat-users> + <user name="tomcat" password="tomcat" roles="tomcat"/> + <user name="user" password="user" roles="user"/> + + <user name="tom" password="secret1" roles="admin,manager"/> + <user name="emma" password="secret2" roles="admin,employee"/> + <user name="bob" password="secret3" roles="admin"/> +</tomcat-users> diff --git a/examples/security-tomcat-user-identitystore/src/main/webapp/WEB-INF/web.xml b/examples/security-tomcat-user-identitystore/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 0000000..a2f32af --- /dev/null +++ b/examples/security-tomcat-user-identitystore/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Copyright (c) 2015, 2020 Oracle and/or its affiliates. All rights reserved. + This program and the accompanying materials are made available under the + terms of the Eclipse Public License v. 2.0, which is available at + http://www.eclipse.org/legal/epl-2.0. + This Source Code may also be made available under the following Secondary + Licenses when the conditions for such availability set forth in the + Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + version 2 with the GNU Classpath Exception, which is available at + https://www.gnu.org/software/classpath/license.html. + SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 +--> + +<web-app + xmlns="http://xmlns.jcp.org/xml/ns/javaee" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" + version="3.1" +> + + <!-- Security constraints --> + + <security-constraint> + <web-resource-collection> + <web-resource-name>Protected admin resource/url</web-resource-name> + <url-pattern>/api/movies/*</url-pattern> + <http-method-omission>GET</http-method-omission> + </web-resource-collection> + <auth-constraint> + <role-name>admin</role-name> + </auth-constraint> + </security-constraint> + +</web-app> \ No newline at end of file diff --git a/examples/security-tomcat-user-identitystore/src/test/java/org/superbiz/movie/BasicAuthFilter.java b/examples/security-tomcat-user-identitystore/src/test/java/org/superbiz/movie/BasicAuthFilter.java new file mode 100644 index 0000000..91d009d --- /dev/null +++ b/examples/security-tomcat-user-identitystore/src/test/java/org/superbiz/movie/BasicAuthFilter.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.superbiz.movie; + +import javax.ws.rs.client.ClientRequestContext; +import javax.ws.rs.client.ClientRequestFilter; +import java.io.IOException; +import java.util.Base64; + +import static javax.ws.rs.core.HttpHeaders.AUTHORIZATION; + +public class BasicAuthFilter implements ClientRequestFilter { + private final String username; + private final String password; + + public BasicAuthFilter(final String username, final String password) { + this.username = username; + this.password = password; + } + + @Override + public void filter(final ClientRequestContext requestContext) throws IOException { + requestContext.getHeaders() + .add(AUTHORIZATION, + "Basic " + new String(Base64.getEncoder().encode((username + ":" + password).getBytes()))); + } +} diff --git a/examples/security-tomcat-user-identitystore/src/test/java/org/superbiz/movie/MovieResourceTest.java b/examples/security-tomcat-user-identitystore/src/test/java/org/superbiz/movie/MovieResourceTest.java new file mode 100644 index 0000000..c1d21e5 --- /dev/null +++ b/examples/security-tomcat-user-identitystore/src/test/java/org/superbiz/movie/MovieResourceTest.java @@ -0,0 +1,170 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.superbiz.movie; + +import org.apache.tomee.bootstrap.Archive; +import org.apache.tomee.bootstrap.Server; +import org.junit.BeforeClass; +import org.junit.Test; + +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.MediaType; +import java.io.File; +import java.net.URI; + +import static javax.ws.rs.client.Entity.entity; +import static org.junit.Assert.assertEquals; + +public class MovieResourceTest { + + private static URI serverURI; + + @BeforeClass + public static void setup() { + // Add any classes you need to an Archive + // or add them to a jar via any means + final Archive classes = Archive.archive() + .add(Api.class) + .add(Movie.class) + .add(MovieStore.class) + .add(MovieResource.class) + .add(MovieAdminResource.class); + + // Place the classes where you would want + // them in a Tomcat install + final Server server = Server.builder() + // This effectively creates a webapp called ROOT + .add("webapps/ROOT/WEB-INF/classes", classes) + .add("webapps/ROOT/WEB-INF/web.xml", new File("src/main/webapp/WEB-INF/web.xml")) + .add("conf/tomcat-users.xml", new File("src/main/resources/conf/tomcat-users.xml")) + .build(); + + serverURI = server.getURI(); + } + + @Test + public void getAllMovies() { + final WebTarget target = ClientBuilder.newClient().target(serverURI); + + final Movie[] movies = target.path("/api/movies").request().get(Movie[].class); + + assertEquals(6, movies.length); + + final Movie movie = movies[1]; + assertEquals("Todd Phillips", movie.getDirector()); + assertEquals("Starsky & Hutch", movie.getTitle()); + assertEquals("Action", movie.getGenre()); + assertEquals(2004, movie.getYear()); + assertEquals(2, movie.getId()); + } + + @Test + public void getAllMoviesAuthenticated() { + final WebTarget target = ClientBuilder.newClient() + .target(serverURI) + .register(new BasicAuthFilter("tomcat", "tomcat")); + + final Movie[] movies = target.path("/api/movies").request().get(Movie[].class); + + assertEquals(6, movies.length); + + final Movie movie = movies[1]; + assertEquals("Todd Phillips", movie.getDirector()); + assertEquals("Starsky & Hutch", movie.getTitle()); + assertEquals("Action", movie.getGenre()); + assertEquals(2004, movie.getYear()); + assertEquals(2, movie.getId()); + } + + @Test + public void getMovieAuthenticated() { + final WebTarget target = ClientBuilder.newClient() + .target(serverURI) + .register(new BasicAuthFilter("bob", "secret3")); + + final Movie movie = target.path("/api/movies/2").request().get(Movie.class); + + assertEquals("Todd Phillips", movie.getDirector()); + assertEquals("Starsky & Hutch", movie.getTitle()); + assertEquals("Action", movie.getGenre()); + assertEquals(2004, movie.getYear()); + assertEquals(2, movie.getId()); + } + + @Test + public void getAllMoviesEmma() { + final WebTarget target = ClientBuilder.newClient() + .target(serverURI) + .register(new BasicAuthFilter("emma", "secret2")); + + final Movie[] movies = target.path("/api/movies").request().get(Movie[].class); + + assertEquals(6, movies.length); + + final Movie movie = movies[1]; + assertEquals("Todd Phillips", movie.getDirector()); + assertEquals("Starsky & Hutch", movie.getTitle()); + assertEquals("Action", movie.getGenre()); + assertEquals(2004, movie.getYear()); + assertEquals(2, movie.getId()); + } + + @Test + public void addMovieAdmin() { + final WebTarget target = ClientBuilder.newClient() + .target(serverURI) + .register(new BasicAuthFilter("tom", "secret1")); + + final Movie movie = new Movie("Shanghai Noon", "Tom Dey", "Comedy", 7, 2000); + + final Movie posted = target.path("/api/movies").request() + .post(entity(movie, MediaType.APPLICATION_JSON)) + .readEntity(Movie.class); + + assertEquals("Tom Dey", posted.getDirector()); + assertEquals("Shanghai Noon", posted.getTitle()); + assertEquals("Comedy", posted.getGenre()); + assertEquals(2000, posted.getYear()); + assertEquals(7, posted.getId()); + } + + @Test + public void addMovieNotAuthenticated() { + final WebTarget target = ClientBuilder.newClient() + .target(serverURI); + + final Movie movie = new Movie("Shanghai Noon", "Tom Dey", "Comedy", 7, 2000); + + assertEquals(401, target.path("/api/movies").request() + .post(entity(movie, MediaType.APPLICATION_JSON)).getStatus()); + + } + + @Test + public void addMovieWrongPermission() { + final WebTarget target = ClientBuilder.newClient() + .target(serverURI) + .register(new BasicAuthFilter("tomcat", "tomcat")); + + final Movie movie = new Movie("Shanghai Noon", "Tom Dey", "Comedy", 7, 2000); + + assertEquals(403, target.path("/api/movies").request() + .post(entity(movie, MediaType.APPLICATION_JSON)).getStatus()); + + } +}