http://git-wip-us.apache.org/repos/asf/tomee/blob/b4a44e40/examples/rest-mp-jwt/src/main/webapp/app/js/view/container.js ---------------------------------------------------------------------- diff --git a/examples/rest-mp-jwt/src/main/webapp/app/js/view/container.js b/examples/rest-mp-jwt/src/main/webapp/app/js/view/container.js new file mode 100644 index 0000000..73a57f1 --- /dev/null +++ b/examples/rest-mp-jwt/src/main/webapp/app/js/view/container.js @@ -0,0 +1,62 @@ +/** + * + * 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. + */ + +(function () { + 'use strict'; + + var deps = ['app/js/templates', 'app/js/i18n', 'lib/backbone']; + define(deps, function (templates) { + + var View = Backbone.View.extend({ + el: 'body', + + showView: function (view) { + var me = this; + var contentarea = me.$('.ux-contentarea'); + if (me.currentView) { + me.currentView.$el.detach(); + } + me.currentView = view; + me.currentView.render(); + contentarea.append(me.currentView.el); + if (view.renderCallback) { + view.renderCallback(); + } + me.$('.ux-app-menu-item').removeClass('active'); + var myMenuItem = me.$('li.ux-app-menu-item.' + view.className); + myMenuItem.addClass('active'); + }, + + render: function () { + if (this.options.isRendered) { + return this; + } + var html = templates.getValue('container', { + userName: '' + }); + this.$el.html(html); + + // render it only once + this.options.isRendered = true; + return this; + } + }); + + return new View({}); + }); +}());
http://git-wip-us.apache.org/repos/asf/tomee/blob/b4a44e40/examples/rest-mp-jwt/src/main/webapp/app/js/view/movie.js ---------------------------------------------------------------------- diff --git a/examples/rest-mp-jwt/src/main/webapp/app/js/view/movie.js b/examples/rest-mp-jwt/src/main/webapp/app/js/view/movie.js new file mode 100644 index 0000000..0417945 --- /dev/null +++ b/examples/rest-mp-jwt/src/main/webapp/app/js/view/movie.js @@ -0,0 +1,75 @@ +/** + * + * 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. + */ + +(function () { + 'use strict'; + + var deps = ['app/js/templates', 'lib/underscore', 'lib/backbone', 'app/js/id']; + define(deps, function (templates, underscore) { + + var View = Backbone.View.extend({ + tagName: 'div', + className: 'modal ux-movie-window', + events: { + 'click .ux-application': function (evt) { + evt.preventDefault(); + var me = this; + me.trigger('show-application', {}); + }, + 'click .ux-close': function (evt) { + evt.preventDefault(); + var me = this; + me.remove(); + }, + 'click .ux-save': function (evt) { + evt.preventDefault(); + var me = this; + var model = me.model; + + function set(name) { + var field = $(me.$el.find('.ux-' + name).get(0)); + model.set(name, field.val()); + } + + set('title'); + set('director'); + set('genre'); + set('rating'); + set('year'); + me.trigger('save-model', { + model: model + }); + } + }, + render: function () { + var me = this; + me.$el.empty(); + me.$el.append(templates.getValue('movie', { + title: me.model.get('title'), + director: me.model.get('director'), + genre: me.model.get('genre'), + rating: me.model.get('rating'), + year: me.model.get('year'), + currentYear: new Date().getFullYear() + })); + return me; + } + }); + return View; + }); +}()); http://git-wip-us.apache.org/repos/asf/tomee/blob/b4a44e40/examples/rest-mp-jwt/src/main/webapp/index.jsp ---------------------------------------------------------------------- diff --git a/examples/rest-mp-jwt/src/main/webapp/index.jsp b/examples/rest-mp-jwt/src/main/webapp/index.jsp new file mode 100644 index 0000000..63d3e42 --- /dev/null +++ b/examples/rest-mp-jwt/src/main/webapp/index.jsp @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<!-- +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. +--> +<%@ page contentType="text/html;charset=UTF-8" language="java" %> +<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %> +<html lang="en"> +<head> + <meta charset="utf-8"> + <title>Moviefun</title> + <link href="<c:url value='/webjars/bootstrap/3.1.0/css/bootstrap.min.css'/>" rel="stylesheet"> + <link href="<c:url value='/app/app.less'/>" rel="stylesheet/less" type="text/css"> + <script src="<c:url value='/webjars/requirejs/2.1.10/require.min.js'/>"></script> + <script type="text/javascript"> + window.ux = window.ux || {}; + window.ux.SESSION_ID = "<%=request.getSession().getId()%>"; + window.ux.ROOT_URL = "<c:url value='/'/>".replace(';jsessionid=' + window.ux.SESSION_ID, ''); + </script> + <script src="<c:url value='/app/config.js'/>"></script> + <script src="<c:url value='/app/js/start.js'/>"></script> +</head> +<body></body> +</html> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tomee/blob/b4a44e40/examples/rest-mp-jwt/src/test/java/org/superbiz/moviefun/MoviesTest.java ---------------------------------------------------------------------- diff --git a/examples/rest-mp-jwt/src/test/java/org/superbiz/moviefun/MoviesTest.java b/examples/rest-mp-jwt/src/test/java/org/superbiz/moviefun/MoviesTest.java new file mode 100644 index 0000000..b18e64e --- /dev/null +++ b/examples/rest-mp-jwt/src/test/java/org/superbiz/moviefun/MoviesTest.java @@ -0,0 +1,88 @@ +/** + * 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.moviefun; + +import org.apache.cxf.feature.LoggingFeature; +import org.apache.cxf.jaxrs.client.WebClient; +import org.apache.johnzon.jaxrs.JohnzonProvider; +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.ClassLoaderAsset; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.superbiz.moviefun.rest.ApplicationConfig; +import org.superbiz.moviefun.rest.LoadRest; +import org.superbiz.moviefun.rest.MoviesMPJWTConfigurationProvider; +import org.superbiz.moviefun.rest.MoviesRest; +import org.superbiz.rest.GreetingService; + +import java.net.URL; +import java.util.Collection; +import java.util.HashMap; + +import static java.util.Collections.singletonList; + +@RunWith(Arquillian.class) +public class MoviesTest { + + @Deployment(testable = false) + public static WebArchive createDeployment() { + final WebArchive webArchive = ShrinkWrap.create(WebArchive.class, "test.war") + .addClasses(Movie.class, MoviesBean.class, MoviesTest.class, LoadRest.class) + .addClasses(MoviesRest.class, GreetingService.class, ApplicationConfig.class) + .addClass(MoviesMPJWTConfigurationProvider.class) + .addAsWebInfResource(new StringAsset("<beans/>"), "beans.xml") + .addAsResource(new ClassLoaderAsset("META-INF/persistence.xml"), "META-INF/persistence.xml"); + + System.out.println(webArchive.toString(true)); + + return webArchive; + } + + @ArquillianResource + private URL base; + + @Test + public void sthg() throws Exception { + + final WebClient webClient = WebClient + .create(base.toExternalForm(), singletonList(new JohnzonProvider<>()), singletonList(new LoggingFeature()), null); + + webClient + .reset() + .path("/rest/greeting/") + .get(String.class); + + final Collection<? extends Movie> movies = webClient + .reset() + .path("/rest/movies/") + .header("Authorization", "Bearer " + token()) + .getCollection(Movie.class); + + System.out.println(movies); + } + + private String token() throws Exception { + HashMap<String, Long> timeClaims = new HashMap<>(); + return TokenUtils.generateTokenString("/Token1.json", null, timeClaims); + } + +} http://git-wip-us.apache.org/repos/asf/tomee/blob/b4a44e40/examples/rest-mp-jwt/src/test/java/org/superbiz/moviefun/TokenUtils.java ---------------------------------------------------------------------- diff --git a/examples/rest-mp-jwt/src/test/java/org/superbiz/moviefun/TokenUtils.java b/examples/rest-mp-jwt/src/test/java/org/superbiz/moviefun/TokenUtils.java new file mode 100644 index 0000000..5aa34b4 --- /dev/null +++ b/examples/rest-mp-jwt/src/test/java/org/superbiz/moviefun/TokenUtils.java @@ -0,0 +1,257 @@ +/* + * Copyright (c) 2016-2017 Contributors to the Eclipse Foundation + * + * 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.superbiz.moviefun; + +import com.nimbusds.jose.JOSEObjectType; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.JWSHeader; +import com.nimbusds.jose.JWSSigner; +import com.nimbusds.jose.crypto.MACSigner; +import com.nimbusds.jose.crypto.RSASSASigner; +import com.nimbusds.jwt.JWTClaimsSet; +import com.nimbusds.jwt.SignedJWT; +import net.minidev.json.JSONObject; +import net.minidev.json.parser.JSONParser; +import org.eclipse.microprofile.jwt.Claims; + +import java.io.InputStream; +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +import static net.minidev.json.parser.JSONParser.DEFAULT_PERMISSIVE_MODE; + +/** + * Utilities for generating a JWT for testing + */ +public class TokenUtils { + private TokenUtils() { + } + + /** + * Utility method to generate a JWT string from a JSON resource file that is signed by the privateKey.pem + * test resource key. + * + * @param jsonResName - name of test resources file + * @return the JWT string + * @throws Exception on parse failure + */ + public static String generateTokenString(String jsonResName) throws Exception { + return generateTokenString(jsonResName, Collections.emptySet()); + } + + /** + * Utility method to generate a JWT string from a JSON resource file that is signed by the privateKey.pem + * test resource key, possibly with invalid fields. + * + * @param jsonResName - name of test resources file + * @param invalidClaims - the set of claims that should be added with invalid values to test failure modes + * @return the JWT string + * @throws Exception on parse failure + */ + public static String generateTokenString(String jsonResName, Set<InvalidClaims> invalidClaims) throws Exception { + return generateTokenString(jsonResName, invalidClaims, null); + } + + /** + * Utility method to generate a JWT string from a JSON resource file that is signed by the privateKey.pem + * test resource key, possibly with invalid fields. + * + * @param jsonResName - name of test resources file + * @param invalidClaims - the set of claims that should be added with invalid values to test failure modes + * @param timeClaims - used to return the exp, iat, auth_time claims + * @return the JWT string + * @throws Exception on parse failure + */ + public static String generateTokenString(String jsonResName, Set<InvalidClaims> invalidClaims, Map<String, Long> timeClaims) throws Exception { + if (invalidClaims == null) { + invalidClaims = Collections.emptySet(); + } + InputStream contentIS = TokenUtils.class.getResourceAsStream(jsonResName); + byte[] tmp = new byte[4096]; + int length = contentIS.read(tmp); + byte[] content = new byte[length]; + System.arraycopy(tmp, 0, content, 0, length); + + JSONParser parser = new JSONParser(DEFAULT_PERMISSIVE_MODE); + JSONObject jwtContent = (JSONObject) parser.parse(content); + // Change the issuer to INVALID_ISSUER for failure testing if requested + if (invalidClaims.contains(InvalidClaims.ISSUER)) { + jwtContent.put(Claims.iss.name(), "INVALID_ISSUER"); + } + long currentTimeInSecs = currentTimeInSecs(); + long exp = currentTimeInSecs + 300; + // Check for an input exp to override the default of now + 300 seconds + if (timeClaims != null && timeClaims.containsKey(Claims.exp.name())) { + exp = timeClaims.get(Claims.exp.name()); + } + jwtContent.put(Claims.iat.name(), currentTimeInSecs); + jwtContent.put(Claims.auth_time.name(), currentTimeInSecs); + // If the exp claim is not updated, it will be an old value that should be seen as expired + if (!invalidClaims.contains(InvalidClaims.EXP)) { + jwtContent.put(Claims.exp.name(), exp); + } + if (timeClaims != null) { + timeClaims.put(Claims.iat.name(), currentTimeInSecs); + timeClaims.put(Claims.auth_time.name(), currentTimeInSecs); + timeClaims.put(Claims.exp.name(), exp); + } + + PrivateKey pk; + if (invalidClaims.contains(InvalidClaims.SIGNER)) { + // Generate a new random private key to sign with to test invalid signatures + KeyPair keyPair = generateKeyPair(2048); + pk = keyPair.getPrivate(); + } else { + // Use the test private key associated with the test public key for a valid signature + pk = readPrivateKey("/privateKey.pem"); + } + + // Create RSA-signer with the private key + JWSSigner signer = new RSASSASigner(pk); + JWTClaimsSet claimsSet = JWTClaimsSet.parse(jwtContent); + JWSAlgorithm alg = JWSAlgorithm.RS256; + if (invalidClaims.contains(InvalidClaims.ALG)) { + alg = JWSAlgorithm.HS256; + SecureRandom random = new SecureRandom(); + BigInteger secret = BigInteger.probablePrime(256, random); + signer = new MACSigner(secret.toByteArray()); + } + JWSHeader jwtHeader = new JWSHeader.Builder(alg) + .keyID("/privateKey.pem") + .type(JOSEObjectType.JWT) + .build(); + SignedJWT signedJWT = new SignedJWT(jwtHeader, claimsSet); + signedJWT.sign(signer); + return signedJWT.serialize(); + } + + /** + * Read a PEM encoded private key from the classpath + * + * @param pemResName - key file resource name + * @return PrivateKey + * @throws Exception on decode failure + */ + public static PrivateKey readPrivateKey(String pemResName) throws Exception { + InputStream contentIS = TokenUtils.class.getResourceAsStream(pemResName); + byte[] tmp = new byte[4096]; + int length = contentIS.read(tmp); + return decodePrivateKey(new String(tmp, 0, length)); + } + + /** + * Read a PEM encoded public key from the classpath + * + * @param pemResName - key file resource name + * @return PublicKey + * @throws Exception on decode failure + */ + public static PublicKey readPublicKey(String pemResName) throws Exception { + InputStream contentIS = TokenUtils.class.getResourceAsStream(pemResName); + byte[] tmp = new byte[4096]; + int length = contentIS.read(tmp); + return decodePublicKey(new String(tmp, 0, length)); + } + + /** + * Generate a new RSA keypair. + * + * @param keySize - the size of the key + * @return KeyPair + * @throws NoSuchAlgorithmException on failure to load RSA key generator + */ + public static KeyPair generateKeyPair(int keySize) throws NoSuchAlgorithmException { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(keySize); + return keyPairGenerator.genKeyPair(); + } + + /** + * Decode a PEM encoded private key string to an RSA PrivateKey + * + * @param pemEncoded - PEM string for private key + * @return PrivateKey + * @throws Exception on decode failure + */ + public static PrivateKey decodePrivateKey(String pemEncoded) throws Exception { + pemEncoded = removeBeginEnd(pemEncoded); + byte[] pkcs8EncodedBytes = Base64.getDecoder().decode(pemEncoded); + + // extract the private key + + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8EncodedBytes); + KeyFactory kf = KeyFactory.getInstance("RSA"); + return kf.generatePrivate(keySpec); + } + + /** + * Decode a PEM encoded public key string to an RSA PublicKey + * + * @param pemEncoded - PEM string for private key + * @return PublicKey + * @throws Exception on decode failure + */ + public static PublicKey decodePublicKey(String pemEncoded) throws Exception { + pemEncoded = removeBeginEnd(pemEncoded); + byte[] encodedBytes = Base64.getDecoder().decode(pemEncoded); + + X509EncodedKeySpec spec = new X509EncodedKeySpec(encodedBytes); + KeyFactory kf = KeyFactory.getInstance("RSA"); + return kf.generatePublic(spec); + } + + private static String removeBeginEnd(String pem) { + pem = pem.replaceAll("-----BEGIN (.*)-----", ""); + pem = pem.replaceAll("-----END (.*)----", ""); + pem = pem.replaceAll("\r\n", ""); + pem = pem.replaceAll("\n", ""); + return pem.trim(); + } + + /** + * @return the current time in seconds since epoch + */ + public static int currentTimeInSecs() { + long currentTimeMS = System.currentTimeMillis(); + return (int) (currentTimeMS / 1000); + } + + /** + * Enums to indicate which claims should be set to invalid values for testing failure modes + */ + public enum InvalidClaims { + ISSUER, // Set an invalid issuer + EXP, // Set an invalid expiration + SIGNER, // Sign the token with the incorrect private key + ALG, // Sign the token with the correct private key, but HS + } +} http://git-wip-us.apache.org/repos/asf/tomee/blob/b4a44e40/examples/rest-mp-jwt/src/test/java/org/superbiz/rest/GreetingServiceTest.java ---------------------------------------------------------------------- diff --git a/examples/rest-mp-jwt/src/test/java/org/superbiz/rest/GreetingServiceTest.java b/examples/rest-mp-jwt/src/test/java/org/superbiz/rest/GreetingServiceTest.java new file mode 100644 index 0000000..f38063f --- /dev/null +++ b/examples/rest-mp-jwt/src/test/java/org/superbiz/rest/GreetingServiceTest.java @@ -0,0 +1,61 @@ +/** + * 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.rest; + +import org.apache.cxf.jaxrs.client.WebClient; +import org.apache.openejb.jee.WebApp; +import org.apache.openejb.junit.ApplicationComposer; +import org.apache.openejb.testing.Classes; +import org.apache.openejb.testing.EnableServices; +import org.apache.openejb.testing.Module; +import org.junit.Test; +import org.junit.runner.RunWith; + +import javax.ws.rs.core.MediaType; +import java.io.IOException; + +import static org.junit.Assert.assertEquals; + +@EnableServices(value = "jaxrs", httpDebug = true) +@RunWith(ApplicationComposer.class) +public class GreetingServiceTest { + + @Module + @Classes(GreetingService.class) + public WebApp app() { + return new WebApp().contextRoot("test"); + } + + @Test + public void get() throws IOException { + final String message = WebClient.create("http://localhost:4204") + .path("/test/greeting/") + .accept(MediaType.APPLICATION_JSON_TYPE) + .get(String.class); + assertEquals("Hi Microprofile JWT!", message); + } + + @Test + public void post() throws IOException { + final String message = WebClient.create("http://localhost:4204") + .path("/test/greeting/") + .type(MediaType.APPLICATION_JSON_TYPE) + .accept(MediaType.APPLICATION_JSON_TYPE) + .post("Hi REST!", String.class); + assertEquals("hi rest!", message); + } +} http://git-wip-us.apache.org/repos/asf/tomee/blob/b4a44e40/examples/rest-mp-jwt/src/test/resources/META-INF/application-client.xml ---------------------------------------------------------------------- diff --git a/examples/rest-mp-jwt/src/test/resources/META-INF/application-client.xml b/examples/rest-mp-jwt/src/test/resources/META-INF/application-client.xml new file mode 100644 index 0000000..1e91dca --- /dev/null +++ b/examples/rest-mp-jwt/src/test/resources/META-INF/application-client.xml @@ -0,0 +1 @@ +<application-client/> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tomee/blob/b4a44e40/examples/rest-mp-jwt/src/test/resources/Token1.json ---------------------------------------------------------------------- diff --git a/examples/rest-mp-jwt/src/test/resources/Token1.json b/examples/rest-mp-jwt/src/test/resources/Token1.json new file mode 100644 index 0000000..32b03c8 --- /dev/null +++ b/examples/rest-mp-jwt/src/test/resources/Token1.json @@ -0,0 +1,20 @@ +{ + "iss": "https://server.example.com", + "jti": "a-123", + "sub": "24400320", + "upn": "[email protected]", + "preferred_username": "jdoe", + "aud": "s6BhdRkqt3", + "exp": 1311281970, + "iat": 1311280970, + "auth_time": 1311280969, + "roles": [ + "Echoer" + ], + "groups": [ + "Echoer", + "Tester", + "group1", + "group2" + ] +} http://git-wip-us.apache.org/repos/asf/tomee/blob/b4a44e40/examples/rest-mp-jwt/src/test/resources/Token2.json ---------------------------------------------------------------------- diff --git a/examples/rest-mp-jwt/src/test/resources/Token2.json b/examples/rest-mp-jwt/src/test/resources/Token2.json new file mode 100644 index 0000000..d69f61a --- /dev/null +++ b/examples/rest-mp-jwt/src/test/resources/Token2.json @@ -0,0 +1,12 @@ +{ + "iss": "https://server.example.com", + "jti": "a-123.2", + "sub": "24400320#2", + "upn": "[email protected]", + "preferred_username": "jdoe", + "aud": "s6BhdRkqt3.2", + "exp": 1311281970, + "iat": 1311280970, + "auth_time": 1311280969, + "groups": ["Echoer2", "Tester", "Token2Role", "group1.2", "group2.2"] +} http://git-wip-us.apache.org/repos/asf/tomee/blob/b4a44e40/examples/rest-mp-jwt/src/test/resources/arquillian.xml ---------------------------------------------------------------------- diff --git a/examples/rest-mp-jwt/src/test/resources/arquillian.xml b/examples/rest-mp-jwt/src/test/resources/arquillian.xml new file mode 100644 index 0000000..4744e7a --- /dev/null +++ b/examples/rest-mp-jwt/src/test/resources/arquillian.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<!-- + + 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. +--> +<arquillian + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + > + + <container qualifier="tomee" default="true"> + <configuration> + <property name="httpPort">-1</property> + <property name="stopPort">-1</property> + <property name="classifier">microprofile</property> + <property name="dir">target/apache-tomee-remote</property> + <property name="appWorkingDir">target/arquillian-test-working-dir</property> + </configuration> + </container> +</arquillian> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tomee/blob/b4a44e40/examples/rest-mp-jwt/src/test/resources/privateKey.pem ---------------------------------------------------------------------- diff --git a/examples/rest-mp-jwt/src/test/resources/privateKey.pem b/examples/rest-mp-jwt/src/test/resources/privateKey.pem new file mode 100644 index 0000000..e20d80b --- /dev/null +++ b/examples/rest-mp-jwt/src/test/resources/privateKey.pem @@ -0,0 +1,28 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCWK8UjyoHgPTLa +PLQJ8SoXLLjpHSjtLxMqmzHnFscqhTVVaDpCRCb6e3Ii/WniQTWw8RA7vf4djz4H +OzvlfBFNgvUGZHXDwnmGaNVaNzpHYFMEYBhE8VGGiveSkzqeLZI+Y02G6sQAfDtN +qqzM/l5QX8X34oQFaTBW1r49nftvCpITiwJvWyhkWtXP9RP8sXi1im5Vi3dhupOh +nelk5n0BfajUYIbfHA6ORzjHRbt7NtBl0L2J+0/FUdHyKs6KMlFGNw8O0Dq88qnM +uXoLJiewhg9332W3DFMeOveel+//cvDnRsCRtPgd4sXFPHh+UShkso7+DRsChXa6 +oGGQD3GdAgMBAAECggEAAjfTSZwMHwvIXIDZB+yP+pemg4ryt84iMlbofclQV8hv +6TsI4UGwcbKxFOM5VSYxbNOisb80qasb929gixsyBjsQ8284bhPJR7r0q8h1C+jY +URA6S4pk8d/LmFakXwG9Tz6YPo3pJziuh48lzkFTk0xW2Dp4SLwtAptZY/+ZXyJ6 +96QXDrZKSSM99Jh9s7a0ST66WoxSS0UC51ak+Keb0KJ1jz4bIJ2C3r4rYlSu4hHB +Y73GfkWORtQuyUDa9yDOem0/z0nr6pp+pBSXPLHADsqvZiIhxD/O0Xk5I6/zVHB3 +zuoQqLERk0WvA8FXz2o8AYwcQRY2g30eX9kU4uDQAQKBgQDmf7KGImUGitsEPepF +KH5yLWYWqghHx6wfV+fdbBxoqn9WlwcQ7JbynIiVx8MX8/1lLCCe8v41ypu/eLtP +iY1ev2IKdrUStvYRSsFigRkuPHUo1ajsGHQd+ucTDf58mn7kRLW1JGMeGxo/t32B +m96Af6AiPWPEJuVfgGV0iwg+HQKBgQCmyPzL9M2rhYZn1AozRUguvlpmJHU2DpqS +34Q+7x2Ghf7MgBUhqE0t3FAOxEC7IYBwHmeYOvFR8ZkVRKNF4gbnF9RtLdz0DMEG +5qsMnvJUSQbNB1yVjUCnDAtElqiFRlQ/k0LgYkjKDY7LfciZl9uJRl0OSYeX/qG2 +tRW09tOpgQKBgBSGkpM3RN/MRayfBtmZvYjVWh3yjkI2GbHA1jj1g6IebLB9SnfL +WbXJErCj1U+wvoPf5hfBc7m+jRgD3Eo86YXibQyZfY5pFIh9q7Ll5CQl5hj4zc4Y +b16sFR+xQ1Q9Pcd+BuBWmSz5JOE/qcF869dthgkGhnfVLt/OQzqZluZRAoGAXQ09 +nT0TkmKIvlza5Af/YbTqEpq8mlBDhTYXPlWCD4+qvMWpBII1rSSBtftgcgca9XLB +MXmRMbqtQeRtg4u7dishZVh1MeP7vbHsNLppUQT9Ol6lFPsd2xUpJDc6BkFat62d +Xjr3iWNPC9E9nhPPdCNBv7reX7q81obpeXFMXgECgYEAmk2Qlus3OV0tfoNRqNpe +Mb0teduf2+h3xaI1XDIzPVtZF35ELY/RkAHlmWRT4PCdR0zXDidE67L6XdJyecSt +FdOUH8z5qUraVVebRFvJqf/oGsXc4+ex1ZKUTbY0wqY1y9E39yvB3MaTmZFuuqk8 +f3cg+fr8aou7pr9SHhJlZCU= +-----END RSA PRIVATE KEY----- http://git-wip-us.apache.org/repos/asf/tomee/blob/b4a44e40/examples/rest-mp-jwt/src/test/resources/publicKey.pem ---------------------------------------------------------------------- diff --git a/examples/rest-mp-jwt/src/test/resources/publicKey.pem b/examples/rest-mp-jwt/src/test/resources/publicKey.pem new file mode 100644 index 0000000..a1dc20c --- /dev/null +++ b/examples/rest-mp-jwt/src/test/resources/publicKey.pem @@ -0,0 +1,9 @@ +-----BEGIN RSA PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0CfEq +Fyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+Bzs75XwR +TYL1BmR1w8J5hmjVWjc6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurEAHw7TaqszP5e +UF/F9+KEBWkwVta+PZ37bwqSE4sCb1soZFrVz/UT/LF4tYpuVYt3YbqToZ3pZOZ9 +AX2o1GCG3xwOjkc4x0W7ezbQZdC9iftPxVHR8irOijJRRjcPDtA6vPKpzLl6CyYn +sIYPd99ltwxTHjr3npfv/3Lw50bAkbT4HeLFxTx4flEoZLKO/g0bAoV2uqBhkA9x +nQIDAQAB +-----END RSA PUBLIC KEY----- http://git-wip-us.apache.org/repos/asf/tomee/blob/b4a44e40/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTCDIExtension.java ---------------------------------------------------------------------- diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTCDIExtension.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTCDIExtension.java index 051f05a..ca69b0a 100644 --- a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTCDIExtension.java +++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/cdi/MPJWTCDIExtension.java @@ -18,7 +18,6 @@ package org.apache.tomee.microprofile.jwt.cdi; import org.apache.tomee.microprofile.jwt.MPJWTFilter; import org.apache.tomee.microprofile.jwt.MPJWTInitializer; -import org.apache.tomee.microprofile.jwt.config.JWTAuthContextInfoProvider; import org.eclipse.microprofile.jwt.Claim; import javax.enterprise.event.Observes; @@ -37,24 +36,13 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.function.Consumer; -import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; public class MPJWTCDIExtension implements Extension { - private static final Predicate<InjectionPoint> NOT_PROVIDERS = new Predicate<InjectionPoint>() { - @Override - public boolean test(final InjectionPoint ip) { - return (Class.class.isInstance(ip.getType())) || (ParameterizedType.class.isInstance(ip.getType()) && ((ParameterizedType) ip.getType()).getRawType() != Provider.class); - } - }; - private static final Predicate<InjectionPoint> NOT_INSTANCES = new Predicate<InjectionPoint>() { - @Override - public boolean test(final InjectionPoint ip) { - return (Class.class.isInstance(ip.getType())) || (ParameterizedType.class.isInstance(ip.getType()) && ((ParameterizedType) ip.getType()).getRawType() != Instance.class); - } - }; + private static final Predicate<InjectionPoint> NOT_PROVIDERS = ip -> (Class.class.isInstance(ip.getType())) || (ParameterizedType.class.isInstance(ip.getType()) && ((ParameterizedType) ip.getType()).getRawType() != Provider.class); + private static final Predicate<InjectionPoint> NOT_INSTANCES = ip -> (Class.class.isInstance(ip.getType())) || (ParameterizedType.class.isInstance(ip.getType()) && ((ParameterizedType) ip.getType()).getRawType() != Instance.class); private static final Map<Type, Type> REPLACED_TYPES = new HashMap<>(); static { @@ -79,44 +67,24 @@ public class MPJWTCDIExtension implements Extension { final Set<Type> types = injectionPoints.stream() .filter(NOT_PROVIDERS) .filter(NOT_INSTANCES) - .map(new Function<InjectionPoint, Type>() { - @Override - public Type apply(final InjectionPoint ip) { - return REPLACED_TYPES.getOrDefault(ip.getType(), ip.getType()); - } - }) + .map(ip -> REPLACED_TYPES.getOrDefault(ip.getType(), ip.getType())) .collect(Collectors.<Type>toSet()); final Set<Type> providerTypes = injectionPoints.stream() .filter(NOT_PROVIDERS.negate()) - .map(new Function<InjectionPoint, Type>() { - @Override - public Type apply(final InjectionPoint ip) { - return ((ParameterizedType) ip.getType()).getActualTypeArguments()[0]; - } - }) + .map(ip -> ((ParameterizedType) ip.getType()).getActualTypeArguments()[0]) .collect(Collectors.<Type>toSet()); final Set<Type> instanceTypes = injectionPoints.stream() .filter(NOT_INSTANCES.negate()) - .map(new Function<InjectionPoint, Type>() { - @Override - public Type apply(final InjectionPoint ip) { - return ((ParameterizedType) ip.getType()).getActualTypeArguments()[0]; - } - }) + .map(ip -> ((ParameterizedType) ip.getType()).getActualTypeArguments()[0]) .collect(Collectors.<Type>toSet()); types.addAll(providerTypes); types.addAll(instanceTypes); types.stream() - .map(new Function<Type, ClaimBean>() { - @Override - public ClaimBean apply(final Type type) { - return new ClaimBean<>(bm, type); - } - }) + .map(type -> new ClaimBean<>(bm, type)) .forEach(new Consumer<ClaimBean>() { @Override public void accept(final ClaimBean claimBean) { @@ -129,7 +97,6 @@ public class MPJWTCDIExtension implements Extension { bbd.addAnnotatedType(beanManager.createAnnotatedType(JsonbProducer.class)); bbd.addAnnotatedType(beanManager.createAnnotatedType(MPJWTFilter.class)); bbd.addAnnotatedType(beanManager.createAnnotatedType(MPJWTInitializer.class)); - bbd.addAnnotatedType(beanManager.createAnnotatedType(JWTAuthContextInfoProvider.class)); bbd.addAnnotatedType(beanManager.createAnnotatedType(MPJWTProducer.class)); } http://git-wip-us.apache.org/repos/asf/tomee/blob/b4a44e40/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfoProvider.java ---------------------------------------------------------------------- diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfoProvider.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfoProvider.java deleted file mode 100644 index 9247e04..0000000 --- a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/config/JWTAuthContextInfoProvider.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.apache.tomee.microprofile.jwt.config; - -import javax.enterprise.context.Dependent; -import javax.enterprise.inject.Produces; -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.interfaces.RSAPublicKey; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.X509EncodedKeySpec; -import java.util.Base64; -import java.util.Optional; - -@Dependent -public class JWTAuthContextInfoProvider { - - @Produces - Optional<JWTAuthContextInfo> getOptionalContextInfo() throws NoSuchAlgorithmException, InvalidKeySpecException { - JWTAuthContextInfo contextInfo = new JWTAuthContextInfo(); - - // todo use MP Config to load the configuration - contextInfo.setIssuedBy("https://server.example.com"); - - final String pemEncoded = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0CfEq" + - "Fyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+Bzs75XwR" + - "TYL1BmR1w8J5hmjVWjc6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurEAHw7TaqszP5e" + - "UF/F9+KEBWkwVta+PZ37bwqSE4sCb1soZFrVz/UT/LF4tYpuVYt3YbqToZ3pZOZ9" + - "AX2o1GCG3xwOjkc4x0W7ezbQZdC9iftPxVHR8irOijJRRjcPDtA6vPKpzLl6CyYn" + - "sIYPd99ltwxTHjr3npfv/3Lw50bAkbT4HeLFxTx4flEoZLKO/g0bAoV2uqBhkA9x" + - "nQIDAQAB"; - byte[] encodedBytes = Base64.getDecoder().decode(pemEncoded); - - final X509EncodedKeySpec spec = new X509EncodedKeySpec(encodedBytes); - final KeyFactory kf = KeyFactory.getInstance("RSA"); - final RSAPublicKey pk = (RSAPublicKey) kf.generatePublic(spec); - - contextInfo.setSignerKey(pk); - - return Optional.of(contextInfo); - } - - @Produces - JWTAuthContextInfo getContextInfo() throws InvalidKeySpecException, NoSuchAlgorithmException { - return getOptionalContextInfo().get(); - } -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tomee/blob/b4a44e40/tck/microprofile-tck/jwt/src/main/java/org/apache/tomee/microprofile/jwt/JWTAuthContextInfoProvider.java ---------------------------------------------------------------------- diff --git a/tck/microprofile-tck/jwt/src/main/java/org/apache/tomee/microprofile/jwt/JWTAuthContextInfoProvider.java b/tck/microprofile-tck/jwt/src/main/java/org/apache/tomee/microprofile/jwt/JWTAuthContextInfoProvider.java new file mode 100644 index 0000000..bf0a07f --- /dev/null +++ b/tck/microprofile-tck/jwt/src/main/java/org/apache/tomee/microprofile/jwt/JWTAuthContextInfoProvider.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 + * + * 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.apache.tomee.microprofile.jwt; + +import org.apache.tomee.microprofile.jwt.config.JWTAuthContextInfo; + +import javax.enterprise.context.Dependent; +import javax.enterprise.inject.Produces; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; +import java.util.Optional; + +@Dependent +public class JWTAuthContextInfoProvider { + + @Produces + Optional<JWTAuthContextInfo> getOptionalContextInfo() throws NoSuchAlgorithmException, InvalidKeySpecException { + JWTAuthContextInfo contextInfo = new JWTAuthContextInfo(); + + // todo use MP Config to load the configuration + contextInfo.setIssuedBy("https://server.example.com"); + + final String pemEncoded = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0CfEq" + + "Fyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+Bzs75XwR" + + "TYL1BmR1w8J5hmjVWjc6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurEAHw7TaqszP5e" + + "UF/F9+KEBWkwVta+PZ37bwqSE4sCb1soZFrVz/UT/LF4tYpuVYt3YbqToZ3pZOZ9" + + "AX2o1GCG3xwOjkc4x0W7ezbQZdC9iftPxVHR8irOijJRRjcPDtA6vPKpzLl6CyYn" + + "sIYPd99ltwxTHjr3npfv/3Lw50bAkbT4HeLFxTx4flEoZLKO/g0bAoV2uqBhkA9x" + + "nQIDAQAB"; + byte[] encodedBytes = Base64.getDecoder().decode(pemEncoded); + + final X509EncodedKeySpec spec = new X509EncodedKeySpec(encodedBytes); + final KeyFactory kf = KeyFactory.getInstance("RSA"); + final RSAPublicKey pk = (RSAPublicKey) kf.generatePublic(spec); + + contextInfo.setSignerKey(pk); + + return Optional.of(contextInfo); + } + + @Produces + JWTAuthContextInfo getContextInfo() throws InvalidKeySpecException, NoSuchAlgorithmException { + return getOptionalContextInfo().get(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tomee/blob/b4a44e40/tck/microprofile-tck/jwt/src/test/java/org/apache/tomee/microprofile/jwt/AppDeploymentExtension.java ---------------------------------------------------------------------- diff --git a/tck/microprofile-tck/jwt/src/test/java/org/apache/tomee/microprofile/jwt/AppDeploymentExtension.java b/tck/microprofile-tck/jwt/src/test/java/org/apache/tomee/microprofile/jwt/AppDeploymentExtension.java index cf4e837..f5f2183 100644 --- a/tck/microprofile-tck/jwt/src/test/java/org/apache/tomee/microprofile/jwt/AppDeploymentExtension.java +++ b/tck/microprofile-tck/jwt/src/test/java/org/apache/tomee/microprofile/jwt/AppDeploymentExtension.java @@ -68,7 +68,8 @@ public class AppDeploymentExtension implements LoadableExtension { if (!(appArchive instanceof WebArchive)) { return; } - WebArchive war = WebArchive.class.cast(appArchive); + final WebArchive war = WebArchive.class.cast(appArchive); + war.addClass(JWTAuthContextInfoProvider.class); log.info("Augmented war: \n"+war.toString(true)); }
