Add more implementations and TCK coverage
Project: http://git-wip-us.apache.org/repos/asf/tomee/repo Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/830b3729 Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/830b3729 Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/830b3729 Branch: refs/heads/master Commit: 830b372981330bf028b576b155e497b24c85bf9a Parents: 9c69190 Author: Jean-Louis Monteiro <jeano...@gmail.com> Authored: Tue Feb 27 09:16:44 2018 +0100 Committer: Jean-Louis Monteiro <jeano...@gmail.com> Committed: Tue Feb 27 09:16:44 2018 +0100 ---------------------------------------------------------------------- tck/mp-jwt-embedded/pom.xml | 11 + .../tomee/microprofile/jwt/ClaimLiteral.java | 32 ++ .../jwt/ClaimProviderBeanAttributes.java | 83 ++++ .../microprofile/jwt/ClaimValueProducer.java | 75 ++++ .../microprofile/jwt/ClaimValueWrapper.java | 54 +++ .../jwt/JWTAuthContextInfoProvider.java | 59 +++ .../jwt/JWTCallerPrincipalFactory.java | 2 +- .../microprofile/jwt/JsonValueProducer.java | 111 ++++++ .../apache/tomee/microprofile/jwt/KeyUtils.java | 84 ++++ .../microprofile/jwt/MPJWTCDIExtension.java | 391 +++++++++++++++++++ .../tomee/microprofile/jwt/MPJWTContext.java | 9 +- .../tomee/microprofile/jwt/MPJWTFilter.java | 25 +- .../microprofile/jwt/MPJWTInitializer.java | 7 +- .../tomee/microprofile/jwt/MPJWTProducer.java | 196 ++++++++++ .../microprofile/jwt/RawClaimTypeProducer.java | 69 ++++ .../javax.enterprise.inject.spi.Extension | 1 + ...e.microprofile.jwt.JWTCallerPrincipalFactory | 1 + ...file.jwt.principal.JWTCallerPrincipalFactory | 1 - .../jwt/AppDeploymentExtension.java | 37 ++ ....jboss.arquillian.core.spi.LoadableExtension | 1 + tck/mp-jwt-embedded/src/test/resources/dev.xml | 8 +- 21 files changed, 1237 insertions(+), 20 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tomee/blob/830b3729/tck/mp-jwt-embedded/pom.xml ---------------------------------------------------------------------- diff --git a/tck/mp-jwt-embedded/pom.xml b/tck/mp-jwt-embedded/pom.xml index ddbccec..272f4ad 100644 --- a/tck/mp-jwt-embedded/pom.xml +++ b/tck/mp-jwt-embedded/pom.xml @@ -112,6 +112,16 @@ <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.6.1</version> + <configuration> + <source>1.8</source> + <target>1.8</target>JwSecTest + </configuration> + </plugin> + <!-- + <plugin> + <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.10</version> <executions> @@ -136,6 +146,7 @@ </execution> </executions> </plugin> + --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> http://git-wip-us.apache.org/repos/asf/tomee/blob/830b3729/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/ClaimLiteral.java ---------------------------------------------------------------------- diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/ClaimLiteral.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/ClaimLiteral.java new file mode 100644 index 0000000..5471b2e --- /dev/null +++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/ClaimLiteral.java @@ -0,0 +1,32 @@ +/* + * 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 javax.enterprise.util.AnnotationLiteral; + +import org.eclipse.microprofile.jwt.Claim; +import org.eclipse.microprofile.jwt.Claims; + +public class ClaimLiteral extends AnnotationLiteral<Claim> implements Claim { + public String value() { + return ""; + } + + public Claims standard() { + return Claims.UNKNOWN; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tomee/blob/830b3729/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/ClaimProviderBeanAttributes.java ---------------------------------------------------------------------- diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/ClaimProviderBeanAttributes.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/ClaimProviderBeanAttributes.java new file mode 100644 index 0000000..ce6e97c --- /dev/null +++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/ClaimProviderBeanAttributes.java @@ -0,0 +1,83 @@ +/* + * 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 java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Set; + +import javax.enterprise.inject.spi.BeanAttributes; + +/** + * An implementation of BeanAttributes<Object> that wraps the generic producer BeanAttributes + * to allow the MPJWTExtension to collect the types of all corresponding injection sites + * + */ +public class ClaimProviderBeanAttributes implements BeanAttributes<Object> { + /** + * Decorate the ConfigPropertyProducer BeanAttributes to set the types the producer applies to. This set is collected + * from all injection points annotated with @ConfigProperty. + * + * @param delegate - the original producer method BeanAttributes + * @param types - the full set of @Claim injection point types + */ + public ClaimProviderBeanAttributes(BeanAttributes<Object> delegate, Set<Type> types, Set<Annotation> qualifiers) { + this.delegate = delegate; + this.types = types; + this.qualifiers = qualifiers; + if (types.size() == 0) { + Thread.dumpStack(); + } + } + + @Override + public Set<Type> getTypes() { + return types; + } + + @Override + public Set<Annotation> getQualifiers() { + return qualifiers; + } + + @Override + public Class<? extends Annotation> getScope() { + return delegate.getScope(); + } + + @Override + public String getName() { + return delegate.getName(); + } + + @Override + public Set<Class<? extends Annotation>> getStereotypes() { + return delegate.getStereotypes(); + } + + @Override + public boolean isAlternative() { + return delegate.isAlternative(); + } + + private BeanAttributes<Object> delegate; + + private Set<Type> types; + + private Set<Annotation> qualifiers; + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tomee/blob/830b3729/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/ClaimValueProducer.java ---------------------------------------------------------------------- diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/ClaimValueProducer.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/ClaimValueProducer.java new file mode 100644 index 0000000..ae2338f --- /dev/null +++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/ClaimValueProducer.java @@ -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. + */ +package org.apache.tomee.microprofile.jwt; + +import java.lang.annotation.Annotation; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Optional; + +import javax.enterprise.inject.Produces; +import javax.enterprise.inject.spi.InjectionPoint; + +import org.eclipse.microprofile.jwt.Claim; +import org.eclipse.microprofile.jwt.ClaimValue; +import org.eclipse.microprofile.jwt.Claims; + +/** + * A producer for the ClaimValue<T> wrapper injection sites. + * @param <T> the raw claim type + */ +public class ClaimValueProducer<T> { + + @Produces + @Claim("") + ClaimValue<T> produce(InjectionPoint ip) { + String name = getName(ip); + ClaimValue<Optional<T>> cv = MPJWTProducer.generalClaimValueProducer(name); + ClaimValue<T> returnValue = (ClaimValue<T>) cv; + Optional<T> value = cv.getValue(); + // Pull out the ClaimValue<T> T type, + Type matchType = ip.getType(); + Type actualType = Object.class; + boolean isOptional = false; + if (matchType instanceof ParameterizedType) { + actualType = ((ParameterizedType) matchType).getActualTypeArguments()[0]; + isOptional = matchType.getTypeName().equals(Optional.class.getTypeName()); + if (isOptional) { + actualType = ((ParameterizedType) matchType).getActualTypeArguments()[0]; + } + } + + if (!actualType.getTypeName().startsWith(Optional.class.getTypeName())) { + T nestedValue = value.orElse(null); + ClaimValueWrapper<T> wrapper = new ClaimValueWrapper<>(cv.getName()); + wrapper.setValue(nestedValue); + returnValue = wrapper; + } + return returnValue; + } + + String getName(InjectionPoint ip) { + String name = null; + for (Annotation ann : ip.getQualifiers()) { + if (ann instanceof Claim) { + Claim claim = (Claim) ann; + name = claim.standard() == Claims.UNKNOWN ? claim.value() : claim.standard().name(); + } + } + return name; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tomee/blob/830b3729/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/ClaimValueWrapper.java ---------------------------------------------------------------------- diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/ClaimValueWrapper.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/ClaimValueWrapper.java new file mode 100644 index 0000000..cc16771 --- /dev/null +++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/ClaimValueWrapper.java @@ -0,0 +1,54 @@ +/* + * 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.eclipse.microprofile.jwt.ClaimValue; + +/** + * An implementation of the ClaimValue interface + * + * @param <T> the claim value type + */ +public class ClaimValueWrapper<T> implements ClaimValue<T> { + private String name; + + private T value; + + public ClaimValueWrapper(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public T getValue() { + return value; + } + + public void setValue(T value) { + this.value = value; + } + + @Override + public String toString() { + return String.format("ClaimValueWrapper[@%s], name=%s, value[%s]=%s", Integer.toHexString(hashCode()), + name, value.getClass(), value); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tomee/blob/830b3729/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JWTAuthContextInfoProvider.java ---------------------------------------------------------------------- diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JWTAuthContextInfoProvider.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JWTAuthContextInfoProvider.java new file mode 100644 index 0000000..a173ea2 --- /dev/null +++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JWTAuthContextInfoProvider.java @@ -0,0 +1,59 @@ +/* + * 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 javax.enterprise.context.Dependent; +import javax.enterprise.inject.Produces; +import java.security.interfaces.RSAPublicKey; +import java.util.Optional; + +@Dependent +public class JWTAuthContextInfoProvider { + + @Produces + Optional<JWTAuthContextInfo> getOptionalContextInfo() { + JWTAuthContextInfo contextInfo = new JWTAuthContextInfo(); + + // todo use MP Config to load the configuration + contextInfo.setIssuedBy("https://server.example.com"); + RSAPublicKey pk = null; + try { + pk = (RSAPublicKey) KeyUtils.decodePublicKey("-----BEGIN RSA PUBLIC KEY-----\n" + + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlivFI8qB4D0y2jy0CfEq\n" + + "Fyy46R0o7S8TKpsx5xbHKoU1VWg6QkQm+ntyIv1p4kE1sPEQO73+HY8+Bzs75XwR\n" + + "TYL1BmR1w8J5hmjVWjc6R2BTBGAYRPFRhor3kpM6ni2SPmNNhurEAHw7TaqszP5e\n" + + "UF/F9+KEBWkwVta+PZ37bwqSE4sCb1soZFrVz/UT/LF4tYpuVYt3YbqToZ3pZOZ9\n" + + "AX2o1GCG3xwOjkc4x0W7ezbQZdC9iftPxVHR8irOijJRRjcPDtA6vPKpzLl6CyYn\n" + + "sIYPd99ltwxTHjr3npfv/3Lw50bAkbT4HeLFxTx4flEoZLKO/g0bAoV2uqBhkA9x\n" + + "nQIDAQAB\n" + + "-----END RSA PUBLIC KEY-----\n"); + + } catch (final Exception e) { + e.printStackTrace(); + // todo better handling + throw new RuntimeException(e); + } + contextInfo.setSignerKey(pk); + + return Optional.of(contextInfo); + } + + @Produces + JWTAuthContextInfo getContextInfo() { + return getOptionalContextInfo().get(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tomee/blob/830b3729/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JWTCallerPrincipalFactory.java ---------------------------------------------------------------------- diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JWTCallerPrincipalFactory.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JWTCallerPrincipalFactory.java index a64f95a..cb00cc5 100644 --- a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JWTCallerPrincipalFactory.java +++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JWTCallerPrincipalFactory.java @@ -83,7 +83,7 @@ public abstract class JWTCallerPrincipalFactory { if (instance == null) { ServiceLoader<JWTCallerPrincipalFactory> sl = ServiceLoader.load(JWTCallerPrincipalFactory.class, cl); - URL u = cl.getResource("/META-INF/services/org.eclipse.microprofile.jwt.principal.JWTCallerPrincipalFactory"); + URL u = cl.getResource("/META-INF/services/org.apache.tomee.microprofile.jwt.JWTCallerPrincipalFactory"); System.out.printf("JWTCallerPrincipalFactory, cl=%s, u=%s, sl=%s\n", cl, u, sl); try { for (JWTCallerPrincipalFactory spi : sl) { http://git-wip-us.apache.org/repos/asf/tomee/blob/830b3729/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JsonValueProducer.java ---------------------------------------------------------------------- diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JsonValueProducer.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JsonValueProducer.java new file mode 100644 index 0000000..355b1d5 --- /dev/null +++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/JsonValueProducer.java @@ -0,0 +1,111 @@ +/* + * 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.eclipse.microprofile.jwt.Claim; +import org.eclipse.microprofile.jwt.Claims; + +import javax.enterprise.inject.Produces; +import javax.enterprise.inject.spi.InjectionPoint; +import javax.json.JsonArray; +import javax.json.JsonNumber; +import javax.json.JsonObject; +import javax.json.JsonString; +import javax.json.JsonValue; +import java.lang.annotation.Annotation; +import java.util.Optional; +import java.util.logging.Logger; + +/** + * A producer for JsonValue injection types + */ +public class JsonValueProducer { + private static Logger log = Logger.getLogger(JsonValueProducer.class.getName()); + + @Produces + @Claim("") + public JsonString getJsonString(InjectionPoint ip) { + return getValue(ip); + } + + @Produces + @Claim("") + public Optional<JsonString> getOptionalJsonString(InjectionPoint ip) { + return getOptionalValue(ip); + } + + @Produces + @Claim("") + public JsonNumber getJsonNumber(InjectionPoint ip) { + return getValue(ip); + } + + @Produces + @Claim("") + public Optional<JsonNumber> getOptionalJsonNumber(InjectionPoint ip) { + return getOptionalValue(ip); + } + + @Produces + @Claim("") + public JsonArray getJsonArray(InjectionPoint ip) { + return getValue(ip); + } + + @Produces + @Claim("") + public Optional<JsonArray> getOptionalJsonArray(InjectionPoint ip) { + return getOptionalValue(ip); + } + + @Produces + @Claim("") + public JsonObject getJsonObject(InjectionPoint ip) { + return getValue(ip); + } + + @Produces + @Claim("") + public Optional<JsonObject> getOptionalJsonObject(InjectionPoint ip) { + return getOptionalValue(ip); + } + + public <T extends JsonValue> T getValue(InjectionPoint ip) { + log.fine(String.format("JsonValueProducer(%s).produce", ip)); + String name = getName(ip); + T jsonValue = (T) MPJWTProducer.generalJsonValueProducer(name); + return jsonValue; + } + + public <T extends JsonValue> Optional<T> getOptionalValue(InjectionPoint ip) { + log.fine(String.format("JsonValueProducer(%s).produce", ip)); + String name = getName(ip); + T jsonValue = (T) MPJWTProducer.generalJsonValueProducer(name); + return Optional.ofNullable(jsonValue); + } + + String getName(InjectionPoint ip) { + String name = null; + for (Annotation ann : ip.getQualifiers()) { + if (ann instanceof Claim) { + Claim claim = (Claim) ann; + name = claim.standard() == Claims.UNKNOWN ? claim.value() : claim.standard().name(); + } + } + return name; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tomee/blob/830b3729/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/KeyUtils.java ---------------------------------------------------------------------- diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/KeyUtils.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/KeyUtils.java new file mode 100644 index 0000000..07e00f2 --- /dev/null +++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/KeyUtils.java @@ -0,0 +1,84 @@ +/* + * 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 java.io.InputStream; +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.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; + +public class KeyUtils { + public static PrivateKey readPrivateKey(String pemResName) throws Exception { + InputStream contentIS = KeyUtils.class.getResourceAsStream(pemResName); + byte[] tmp = new byte[4096]; + int length = contentIS.read(tmp); + PrivateKey privateKey = decodePrivateKey(new String(tmp, 0, length)); + return privateKey; + } + + public static PublicKey readPublicKey(String pemResName) throws Exception { + InputStream contentIS = KeyUtils.class.getResourceAsStream(pemResName); + byte[] tmp = new byte[4096]; + int length = contentIS.read(tmp); + PublicKey publicKey = decodePublicKey(new String(tmp, 0, length)); + return publicKey; + } + + public static KeyPair generateKeyPair(int keySize) throws NoSuchAlgorithmException { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(keySize); + KeyPair keyPair = keyPairGenerator.genKeyPair(); + return keyPair; + } + + 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"); + PrivateKey privKey = kf.generatePrivate(keySpec); + return privKey; + } + + 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(.*)KEY-----", ""); + pem = pem.replaceAll("-----END(.*)KEY-----", ""); + pem = pem.replaceAll("\r\n", ""); + pem = pem.replaceAll("\n", ""); + return pem.trim(); + } + private KeyUtils() { + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tomee/blob/830b3729/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTCDIExtension.java ---------------------------------------------------------------------- diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTCDIExtension.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTCDIExtension.java new file mode 100644 index 0000000..aa15a33 --- /dev/null +++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTCDIExtension.java @@ -0,0 +1,391 @@ +/* + * 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.eclipse.microprofile.jwt.Claim; +import org.eclipse.microprofile.jwt.Claims; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.SessionScoped; +import javax.enterprise.event.Observes; +import javax.enterprise.inject.spi.AfterBeanDiscovery; +import javax.enterprise.inject.spi.AfterDeploymentValidation; +import javax.enterprise.inject.spi.BeanAttributes; +import javax.enterprise.inject.spi.BeanManager; +import javax.enterprise.inject.spi.BeforeBeanDiscovery; +import javax.enterprise.inject.spi.DeploymentException; +import javax.enterprise.inject.spi.Extension; +import javax.enterprise.inject.spi.InjectionPoint; +import javax.enterprise.inject.spi.ProcessBeanAttributes; +import javax.enterprise.inject.spi.ProcessInjectionPoint; +import javax.enterprise.inject.spi.ProcessProducer; +import javax.inject.Provider; +import java.lang.annotation.Annotation; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.logging.Logger; + +/** + * A CDI extension that provides a producer for the current authenticated JsonWebToken based on a thread + * local value that is managed by the {@link JWTAuthMechanism} request + * authentication handler. + * <p> + * This also installs the producer methods for the discovered: + * <ul> + * <li>@Claim ClaimValue<T> injection sites.</li> + * <li>@Claim raw type<T> injection sites.</li> + * <li>@Claim JsonValue injection sites.</li> + * </ul> + * + * @see JWTAuthMechanism + */ +public class MPJWTCDIExtension implements Extension { + private static Logger log = Logger.getLogger(MPJWTCDIExtension.class.getName()); + + /** + * Register the MPJWTProducer JsonWebToken producer bean + * + * @param bbd before discovery event + * @param beanManager cdi bean manager + */ + public void observeBeforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd, BeanManager beanManager) { + log.fine(String.format("MPJWTExtension(), added JWTPrincipalProducer")); + bbd.addAnnotatedType(beanManager.createAnnotatedType(TCKTokenParser.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)); + bbd.addAnnotatedType(beanManager.createAnnotatedType(RawClaimTypeProducer.class)); + bbd.addAnnotatedType(beanManager.createAnnotatedType(ClaimValueProducer.class)); + bbd.addAnnotatedType(beanManager.createAnnotatedType(JsonValueProducer.class)); + } + + /** + * Replace the general producer method BeanAttributes with one bound to the collected injection site + * types to properly reflect all of the type locations the producer method applies to. + * + * @param pba the ProcessBeanAttributes + * @see ClaimProviderBeanAttributes + */ + public void addTypeToClaimProducer(@Observes ProcessBeanAttributes pba) { + if (pba.getAnnotated().isAnnotationPresent(Claim.class)) { + Claim claim = pba.getAnnotated().getAnnotation(Claim.class); + if (claim.value().length() == 0 && claim.standard() == Claims.UNKNOWN) { + log.fine(String.format("addTypeToClaimProducer: %s\n", pba.getAnnotated())); + BeanAttributes delegate = pba.getBeanAttributes(); + String name = delegate.getName(); + if (delegate.getTypes().contains(Optional.class)) { + if (providerOptionalTypes.size() == 0) { + providerOptionalTypes.add(Optional.class); + } + pba.setBeanAttributes(new ClaimProviderBeanAttributes(delegate, providerOptionalTypes, providerQualifiers)); + // This is + } else if (name != null && name.startsWith("RawClaimTypeProducer#")) { + if (rawTypes.size() == 0) { + rawTypes.add(Object.class); + } + pba.setBeanAttributes(new ClaimProviderBeanAttributes(delegate, rawTypes, rawTypeQualifiers)); + log.fine(String.format("Setup RawClaimTypeProducer BeanAttributes")); + } + } + } + } + + public void afterDeploymentValidation(@Observes AfterDeploymentValidation event, BeanManager beanManager) { + } + + void doProcessProducers(@Observes ProcessProducer pp) { + } + + /** + * Handle the non-{@linkplain Provider}, {@linkplain org.eclipse.microprofile.jwt.ClaimValue}, and + * {@linkplain javax.json.JsonValue} claim injection types. + * + * @param pip - the injection point event information + * @see RawClaimTypeProducer + */ + void processClaimInjections(@Observes ProcessInjectionPoint pip) { + log.fine(String.format("pipRaw: %s", pip.getInjectionPoint())); + InjectionPoint ip = pip.getInjectionPoint(); + if (ip.getAnnotated().isAnnotationPresent(Claim.class)) { + Claim claim = ip.getAnnotated().getAnnotation(Claim.class); + if (ip.getType() instanceof Class) { + Class rawClass = (Class) ip.getType(); + // Primative types + if (Modifier.isFinal(rawClass.getModifiers())) { + rawTypes.add(ip.getType()); + rawTypeQualifiers.add(claim); + log.fine(String.format("+++ Added Claim raw type: %s", ip.getType())); + Class declaringClass = ip.getMember().getDeclaringClass(); + Annotation[] appScoped = declaringClass.getAnnotationsByType(ApplicationScoped.class); + Annotation[] sessionScoped = declaringClass.getAnnotationsByType(SessionScoped.class); + if ((appScoped != null && appScoped.length > 0) || (sessionScoped != null && sessionScoped.length > 0)) { + String err = String.format("A raw type cannot be injected into application/session scope: IP=%s", ip); + pip.addDefinitionError(new DeploymentException(err)); + } + } + // This handles collections of primative types + } else if (isRawParameterizedType(ip.getType())) { + log.fine(String.format("+++ Added Claim ParameterizedType: %s", ip.getType())); + rawTypes.add(ip.getType()); + rawTypeQualifiers.add(claim); + } + } else { + log.fine(String.format("Skipping pip: %s, type: %s/%s", ip, ip.getType(), ip.getType().getClass())); + } + } + + /** + * Collect the types of all {@linkplain Provider} injection points annotated with {@linkplain Claim}. + * + * @param pip - the injection point event information + */ + void processClaimProviderInjections(@Observes ProcessInjectionPoint<?, ? extends Provider> pip) { + log.fine(String.format("pip: %s", pip.getInjectionPoint())); + final InjectionPoint ip = pip.getInjectionPoint(); + if (ip.getAnnotated().isAnnotationPresent(Claim.class)) { + Claim claim = ip.getAnnotated().getAnnotation(Claim.class); + if (claim.value().length() == 0 && claim.standard() == Claims.UNKNOWN) { + pip.addDefinitionError(new DeploymentException("@Claim at: " + ip + " has no name or valid standard enum setting")); + } + boolean usesEnum = claim.standard() != Claims.UNKNOWN; + final String claimName = usesEnum ? claim.standard().name() : claim.value(); + log.fine(String.format("Checking Provider Claim(%s), ip: %s", claimName, ip)); + ClaimIP claimIP = claims.get(claimName); + Type matchType = ip.getType(); + // The T from the Provider<T> injection site + Type actualType = ((ParameterizedType) matchType).getActualTypeArguments()[0]; + // Don't add Optional or JsonValue as this is handled specially + if (!optionalOrJsonValue(actualType)) { + rawTypes.add(actualType); + } else if (!actualType.getTypeName().startsWith("javax.json.Json")) { + // Validate that this is not an Optional<JsonValue> + Type innerType = ((ParameterizedType) actualType).getActualTypeArguments()[0]; + if (!innerType.getTypeName().startsWith("javax.json.Json")) { + providerOptionalTypes.add(actualType); + providerQualifiers.add(claim); + } + } + rawTypeQualifiers.add(claim); + ClaimIPType key = new ClaimIPType(claimName, actualType); + if (claimIP == null) { + claimIP = new ClaimIP(actualType, actualType, false, claim); + claimIP.setProviderSite(true); + claims.put(key, claimIP); + } + claimIP.getInjectionPoints().add(ip); + log.fine(String.format("+++ Added Provider Claim(%s) ip: %s", claimName, ip)); + + } + } + + /** + * Create producer methods for each ClaimValue injection site + * + * @param event - AfterBeanDiscovery + * @param beanManager - CDI bean manager + */ + void observesAfterBeanDiscovery(@Observes final AfterBeanDiscovery event, final BeanManager beanManager) { + log.fine(String.format("observesAfterBeanDiscovery, %s", claims)); + installClaimValueProducerMethodsViaSyntheticBeans(event, beanManager); + + //installClaimValueProducesViaTemplateType(event, beanManager); + } + + /** + * Create a synthetic bean with a custom Producer for the non-Provider injection sites. + * + * @param event - AfterBeanDiscovery + * @param beanManager - CDI bean manager + */ + private void installClaimValueProducerMethodsViaSyntheticBeans(final AfterBeanDiscovery event, final BeanManager beanManager) { + + } + + private boolean optionalOrJsonValue(Type type) { + boolean isOptionOrJson = type.getTypeName().startsWith(Optional.class.getTypeName()) + | type.getTypeName().startsWith("javax.json.Json"); + return isOptionOrJson; + } + + private boolean isRawParameterizedType(Type type) { + boolean isRawParameterizedType = false; + if (type instanceof ParameterizedType) { + ParameterizedType ptype = ParameterizedType.class.cast(type); + Type rawType = ptype.getRawType(); + String rawTypeName = rawType.getTypeName(); + isRawParameterizedType = !rawTypeName.startsWith("org.eclipse.microprofile.jwt"); + } + return isRawParameterizedType; + } + + /** + * A map of claim,type pairs to the injection site information + */ + private HashMap<ClaimIPType, ClaimIP> claims = new HashMap<>(); + + private Set<Type> providerOptionalTypes = new HashSet<>(); + + private Set<Type> providerTypes = new HashSet<>(); + + private Set<Type> rawTypes = new HashSet<>(); + + private Set<Annotation> rawTypeQualifiers = new HashSet<>(); + + private Set<Annotation> providerQualifiers = new HashSet<>(); + + /** + * A key for a claim,injection site type pair + */ + public static class ClaimIPType implements Comparable<ClaimIPType> { + public ClaimIPType(String claimName, Type ipType) { + this.claimName = claimName; + this.ipType = ipType; + } + + /** + * Order the @Claim ClaimValue<T> on the @Claim.value and then T type name + * + * @param o - ClaimIP to compare to + * @return the ordering of this claim relative to o + */ + @Override + public int compareTo(ClaimIPType o) { + int compareTo = claimName.compareTo(o.claimName); + if (compareTo == 0) { + compareTo = ipType.getTypeName().compareTo(o.ipType.getTypeName()); + } + return compareTo; + } + + private String claimName; + + private Type ipType; + } + + /** + * The representation of an @Claim annotated injection site + */ + public static class ClaimIP { + /** + * Create a ClaimIP from the injection site information + * + * @param matchType - the outer type of the injection site + * @param valueType - the parameterized type of the injection site + * @param isOptional - is the injection site an Optional + * @param claim - the Claim qualifier + */ + public ClaimIP(Type matchType, Type valueType, boolean isOptional, Claim claim) { + this.matchType = matchType; + this.valueType = valueType; + this.claim = claim; + } + + public Type getMatchType() { + return matchType; + } + + public String getClaimName() { + return claim.standard() == Claims.UNKNOWN ? claim.value() : claim.standard().name(); + } + + public Claim getClaim() { + return claim; + } + + public Type getValueType() { + return valueType; + } + + public boolean isOptional() { + return isOptional; + } + + public boolean isProviderSite() { + return isProviderSite; + } + + public void setProviderSite(boolean providerSite) { + this.isProviderSite = providerSite; + } + + public boolean isNonStandard() { + return isNonStandard; + } + + public void setNonStandard(boolean nonStandard) { + isNonStandard = nonStandard; + } + + public boolean isJsonValue() { + return isJsonValue; + } + + public void setJsonValue(boolean jsonValue) { + isJsonValue = jsonValue; + } + + public Set<InjectionPoint> getInjectionPoints() { + return injectionPoints; + } + + @Override + public String toString() { + return "ClaimIP{" + + "type=" + matchType + + ", claim=" + claim + + ", ips=" + injectionPoints + + '}'; + } + + /** + * The injection site value type + */ + private Type matchType; + + /** + * The actual type of of the ParameterizedType matchType + */ + private Type valueType; + + /** + * Is valueType actually wrapped in an Optional + */ + private boolean isOptional; + + private boolean isProviderSite; + + private boolean isNonStandard; + + private boolean isJsonValue; + + /** + * The injection site @Claim annotation value + */ + private Claim claim; + + /** + * The location that share the @Claim/type combination + */ + private HashSet<InjectionPoint> injectionPoints = new HashSet<>(); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tomee/blob/830b3729/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTContext.java ---------------------------------------------------------------------- diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTContext.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTContext.java index bffccf2..07dfe0b 100644 --- a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTContext.java +++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTContext.java @@ -27,12 +27,11 @@ import java.util.function.Predicate; /** * Responsible for holding the runtime model */ -@ApplicationScoped public class MPJWTContext { - private final ConcurrentMap<MPJWTConfigKey, MPJWTConfigValue> configuration = new ConcurrentHashMap<>(); + private static final ConcurrentMap<MPJWTConfigKey, MPJWTConfigValue> configuration = new ConcurrentHashMap<>(); - public MPJWTConfigValue addMapping(final MPJWTConfigKey key, final MPJWTConfigValue value) { + public static MPJWTConfigValue addMapping(final MPJWTConfigKey key, final MPJWTConfigValue value) { Objects.requireNonNull(key, "MP JWT Key is required"); Objects.requireNonNull(value, "MP JWT Value is required"); @@ -44,12 +43,12 @@ public class MPJWTContext { return value; } - public Optional<MPJWTConfigValue> get(final MPJWTConfigKey key) { + public static Optional<MPJWTConfigValue> get(final MPJWTConfigKey key) { Objects.requireNonNull(key, "MP JWT Key is required to retrieve the configuration"); return Optional.ofNullable(configuration.get(key)); } - public Optional<Map.Entry<MPJWTConfigKey, MPJWTConfigValue>> findFirst(final String path) { + public static Optional<Map.Entry<MPJWTConfigKey, MPJWTConfigValue>> findFirst(final String path) { return configuration.entrySet() .stream() .filter(new Predicate<ConcurrentMap.Entry<MPJWTConfigKey, MPJWTConfigValue>>() { http://git-wip-us.apache.org/repos/asf/tomee/blob/830b3729/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java ---------------------------------------------------------------------- diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java index 092ad9d..a8f7cb4 100644 --- a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java +++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java @@ -38,9 +38,6 @@ import java.util.Optional; public class MPJWTFilter implements Filter { @Inject - private MPJWTContext context; - - @Inject private JWTAuthContextInfo authContextInfo; @Override @@ -54,16 +51,31 @@ public class MPJWTFilter implements Filter { final HttpServletRequest httpServletRequest = (HttpServletRequest) request; final Optional<Map.Entry<MPJWTContext.MPJWTConfigKey, MPJWTContext.MPJWTConfigValue>> first = - context.findFirst(httpServletRequest.getRequestURI()); + MPJWTContext.findFirst(httpServletRequest.getRequestURI()); - if (first.isPresent()) { // nothing found in the context + if (!first.isPresent()) { // nothing found in the context chain.doFilter(request, response); + return; } // todo get JWT and do validation // todo not sure what to do with the realm - final JsonWebToken jsonWebToken = null; // will be build during validation + final String authorizationHeader = ((HttpServletRequest) request).getHeader("Authorization"); + final String token = authorizationHeader.substring("bearer ".length()); + final JsonWebToken jsonWebToken; + try { + jsonWebToken = DefaultJWTCallerPrincipalFactory.instance().parse(token, authContextInfo); + + } catch (final ParseException e) { + // todo properly handle the exception as required per spec + e.printStackTrace(); + throw new RuntimeException(e); + } + + // associate with the producer. Should not be needed. + // todo We should be able to retrieve it based on the HTTP Servlet Request in the producer + MPJWTProducer.setJWTPrincipal(jsonWebToken); // now wrap the httpServletRequest and override the principal so CXF can propagate into the SecurityContext chain.doFilter(new HttpServletRequestWrapper(httpServletRequest) { @@ -85,6 +97,7 @@ public class MPJWTFilter implements Filter { }, response); + } @Override http://git-wip-us.apache.org/repos/asf/tomee/blob/830b3729/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTInitializer.java ---------------------------------------------------------------------- diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTInitializer.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTInitializer.java index dc3d7ba..bb8bb16 100644 --- a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTInitializer.java +++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTInitializer.java @@ -34,9 +34,6 @@ import java.util.Set; @HandlesTypes(LoginConfig.class) public class MPJWTInitializer implements ServletContainerInitializer { - @Inject - private MPJWTContext context; - @Override public void onStartup(final Set<Class<?>> classes, final ServletContext ctx) throws ServletException { @@ -59,11 +56,13 @@ public class MPJWTInitializer implements ServletContainerInitializer { final FilterRegistration.Dynamic mpJwtFilter = ctx.addFilter("mp-jwt-filter", MPJWTFilter.class); mpJwtFilter.setAsyncSupported(true); + mpJwtFilter.addMappingForUrlPatterns(null, false, "/*"); - context.addMapping( + MPJWTContext.addMapping( new MPJWTContext.MPJWTConfigKey( ctx.getContextPath(), applicationPath == null ? "" : applicationPath.value()), + new MPJWTContext.MPJWTConfigValue( loginConfig.authMethod(), loginConfig.realmName()) http://git-wip-us.apache.org/repos/asf/tomee/blob/830b3729/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTProducer.java ---------------------------------------------------------------------- diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTProducer.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTProducer.java new file mode 100644 index 0000000..195f323 --- /dev/null +++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTProducer.java @@ -0,0 +1,196 @@ +/* + * 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.eclipse.microprofile.jwt.ClaimValue; +import org.eclipse.microprofile.jwt.JsonWebToken; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.Destroyed; +import javax.enterprise.context.Initialized; +import javax.enterprise.context.RequestScoped; +import javax.enterprise.event.Observes; +import javax.enterprise.inject.Produces; +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.json.JsonValue; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.logging.Logger; + +/** + * A class that tracks the current validated MP-JWT and associated JsonWebToken via a thread + * local to provide a @RequestScoped JsonWebToken producer method. + * <p> + * It also provides utility methods for access the current JsonWebToken claim values. + */ +@ApplicationScoped +public class MPJWTProducer { + private static Logger log = Logger.getLogger(MPJWTProducer.class.getName()); + private static final String TMP = "tmp"; + private static ThreadLocal<JsonWebToken> currentPrincipal = new ThreadLocal<>(); + + public static void setJWTPrincipal(JsonWebToken principal) { + currentPrincipal.set(principal); + } + + public static JsonWebToken getJWTPrincpal() { + return currentPrincipal.get(); + } + + @PostConstruct + void init() { + log.fine("MPJWTProducer initialized"); + } + + void observeRequestInitialized(@Observes @Initialized(RequestScoped.class) Object event) { + log.finest(String.format("observeRequestInitialized, event=%s", event)); + } + + void observeRequestDestroyed(@Observes @Destroyed(RequestScoped.class) Object event) { + log.finest(String.format("observeRequestDestroyed, event=%s", event)); + } + + /** + * The @RequestScoped producer method for the current JsonWebToken + * + * @return + */ + @Produces + @RequestScoped + JsonWebToken currentPrincipalOrNull() { + return currentPrincipal.get(); + } + + /** + * A utility method for accessing a claim from the current JsonWebToken as a ClaimValue<Optional<T>> object. + * + * @param name - name of the claim + * @param <T> expected actual type of the claim + * @return the claim value wrapper object + */ + static <T> ClaimValue<Optional<T>> generalClaimValueProducer(String name) { + ClaimValueWrapper<Optional<T>> wrapper = new ClaimValueWrapper<>(name); + T value = getValue(name, false); + Optional<T> optValue = Optional.ofNullable(value); + wrapper.setValue(optValue); + return wrapper; + } + + /** + * Return the indicated claim value as a JsonValue + * + * @param name - name of the claim + * @return a JsonValue wrapper + */ + static JsonValue generalJsonValueProducer(String name) { + Object value = getValue(name, false); + JsonValue jsonValue = wrapValue(value); + return jsonValue; + } + + public static <T> T getValue(String name, boolean isOptional) { + JsonWebToken jwt = getJWTPrincpal(); + if (jwt == null) { + log.fine(String.format("getValue(%s), null JsonWebToken", name)); + return null; + } + + Optional<T> claimValue = jwt.claim(name); + if (!isOptional && !claimValue.isPresent()) { + log.fine(String.format("Failed to find Claim for: %s", name)); + } + log.fine(String.format("getValue(%s), isOptional=%s, claimValue=%s", name, isOptional, claimValue)); + return claimValue.orElse(null); + } + + static JsonObject replaceMap(Map<String, Object> map) { + JsonObjectBuilder builder = Json.createObjectBuilder(); + for (Map.Entry<String, Object> entry : map.entrySet()) { + Object entryValue = entry.getValue(); + if (entryValue instanceof Map) { + JsonObject entryJsonObject = replaceMap((Map<String, Object>) entryValue); + builder.add(entry.getKey(), entryJsonObject); + } else if (entryValue instanceof List) { + JsonArray array = (JsonArray) wrapValue(entryValue); + builder.add(entry.getKey(), array); + } else if (entryValue instanceof Long || entryValue instanceof Integer) { + long lvalue = ((Number) entryValue).longValue(); + builder.add(entry.getKey(), lvalue); + } else if (entryValue instanceof Double || entryValue instanceof Float) { + double dvalue = ((Number) entryValue).doubleValue(); + builder.add(entry.getKey(), dvalue); + } else if (entryValue instanceof Boolean) { + boolean flag = ((Boolean) entryValue).booleanValue(); + builder.add(entry.getKey(), flag); + } else if (entryValue instanceof String) { + builder.add(entry.getKey(), entryValue.toString()); + } + } + return builder.build(); + } + + static JsonValue wrapValue(Object value) { + JsonValue jsonValue = null; + if (value instanceof JsonValue) { + // This may already be a JsonValue + jsonValue = (JsonValue) value; + } else if (value instanceof String) { + jsonValue = Json.createObjectBuilder() + .add(TMP, value.toString()) + .build() + .getJsonString(TMP); + } else if (value instanceof Number) { + Number number = (Number) value; + if ((number instanceof Long) || (number instanceof Integer)) { + jsonValue = Json.createObjectBuilder() + .add(TMP, number.longValue()) + .build() + .getJsonNumber(TMP); + } else { + jsonValue = Json.createObjectBuilder() + .add(TMP, number.doubleValue()) + .build() + .getJsonNumber(TMP); + } + } else if (value instanceof Boolean) { + Boolean flag = (Boolean) value; + jsonValue = flag ? JsonValue.TRUE : JsonValue.FALSE; + } else if (value instanceof Collection) { + JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); + Collection list = (Collection) value; + for (Object element : list) { + if (element instanceof String) { + arrayBuilder.add(element.toString()); + } else { + JsonValue jvalue = wrapValue(element); + arrayBuilder.add(jvalue); + } + } + jsonValue = arrayBuilder.build(); + } else if (value instanceof Map) { + jsonValue = replaceMap((Map) value); + } + return jsonValue; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tomee/blob/830b3729/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/RawClaimTypeProducer.java ---------------------------------------------------------------------- diff --git a/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/RawClaimTypeProducer.java b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/RawClaimTypeProducer.java new file mode 100644 index 0000000..b52912c --- /dev/null +++ b/tck/mp-jwt-embedded/src/main/java/org/apache/tomee/microprofile/jwt/RawClaimTypeProducer.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 + * + * 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.eclipse.microprofile.jwt.Claim; +import org.eclipse.microprofile.jwt.ClaimValue; +import org.eclipse.microprofile.jwt.Claims; + +import javax.enterprise.inject.Produces; +import javax.enterprise.inject.spi.InjectionPoint; +import javax.inject.Named; +import java.lang.annotation.Annotation; +import java.util.Optional; +import java.util.logging.Logger; + +/** + * + */ +public class RawClaimTypeProducer { + private static Logger log = Logger.getLogger(RawClaimTypeProducer.class.getName()); + + @Produces + @Claim("") + @Named("RawClaimTypeProducer#getValue") + public Object getValue(InjectionPoint ip) { + log.fine(String.format("getValue(%s)", ip)); + String name = getName(ip); + ClaimValue<Optional<Object>> cv = MPJWTProducer.generalClaimValueProducer(name); + Optional<Object> value = cv.getValue(); + Object returnValue = value.orElse(null); + return returnValue; + } + + @Produces + @Claim("") + @Named("RawClaimTypeProducer#getOptionalValue") + public Optional getOptionalValue(InjectionPoint ip) { + log.fine(String.format("getOptionalValue(%s)", ip)); + String name = getName(ip); + ClaimValue<Optional<Object>> cv = MPJWTProducer.generalClaimValueProducer(name); + Optional<Object> value = cv.getValue(); + return value; + } + + String getName(InjectionPoint ip) { + String name = null; + for (Annotation ann : ip.getQualifiers()) { + if (ann instanceof Claim) { + Claim claim = (Claim) ann; + name = claim.standard() == Claims.UNKNOWN ? claim.value() : claim.standard().name(); + } + } + return name; + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tomee/blob/830b3729/tck/mp-jwt-embedded/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension ---------------------------------------------------------------------- diff --git a/tck/mp-jwt-embedded/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension b/tck/mp-jwt-embedded/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension new file mode 100644 index 0000000..5e3bccc --- /dev/null +++ b/tck/mp-jwt-embedded/src/main/resources/META-INF/services/javax.enterprise.inject.spi.Extension @@ -0,0 +1 @@ +org.apache.tomee.microprofile.jwt.MPJWTCDIExtension \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tomee/blob/830b3729/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.apache.tomee.microprofile.jwt.JWTCallerPrincipalFactory ---------------------------------------------------------------------- diff --git a/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.apache.tomee.microprofile.jwt.JWTCallerPrincipalFactory b/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.apache.tomee.microprofile.jwt.JWTCallerPrincipalFactory new file mode 100644 index 0000000..67f39db --- /dev/null +++ b/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.apache.tomee.microprofile.jwt.JWTCallerPrincipalFactory @@ -0,0 +1 @@ +org.apache.tomee.microprofile.jwt.DefaultJWTCallerPrincipalFactory \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tomee/blob/830b3729/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.eclipse.microprofile.jwt.principal.JWTCallerPrincipalFactory ---------------------------------------------------------------------- diff --git a/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.eclipse.microprofile.jwt.principal.JWTCallerPrincipalFactory b/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.eclipse.microprofile.jwt.principal.JWTCallerPrincipalFactory deleted file mode 100644 index 67f39db..0000000 --- a/tck/mp-jwt-embedded/src/main/resources/META-INF/services/org.eclipse.microprofile.jwt.principal.JWTCallerPrincipalFactory +++ /dev/null @@ -1 +0,0 @@ -org.apache.tomee.microprofile.jwt.DefaultJWTCallerPrincipalFactory \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tomee/blob/830b3729/tck/mp-jwt-embedded/src/test/java/org/apache/tomee/microprofile/jwt/AppDeploymentExtension.java ---------------------------------------------------------------------- diff --git a/tck/mp-jwt-embedded/src/test/java/org/apache/tomee/microprofile/jwt/AppDeploymentExtension.java b/tck/mp-jwt-embedded/src/test/java/org/apache/tomee/microprofile/jwt/AppDeploymentExtension.java new file mode 100644 index 0000000..4ade364 --- /dev/null +++ b/tck/mp-jwt-embedded/src/test/java/org/apache/tomee/microprofile/jwt/AppDeploymentExtension.java @@ -0,0 +1,37 @@ +package org.apache.tomee.microprofile.jwt; + +import org.jboss.arquillian.container.spi.client.deployment.DeploymentDescription; +import org.jboss.arquillian.container.test.impl.client.deployment.AnnotationDeploymentScenarioGenerator; +import org.jboss.arquillian.container.test.spi.client.deployment.DeploymentScenarioGenerator; +import org.jboss.arquillian.core.spi.LoadableExtension; +import org.jboss.arquillian.test.spi.TestClass; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.spec.WebArchive; + +import java.util.Collections; +import java.util.List; + +public class AppDeploymentExtension implements LoadableExtension { + @Override + public void register(final ExtensionBuilder extensionBuilder) { + extensionBuilder.service(DeploymentScenarioGenerator.class, SimpleDeploymentScenarioGenerator.class); + } + + public static class SimpleDeploymentScenarioGenerator implements DeploymentScenarioGenerator { + + private final DeploymentScenarioGenerator standard = new AnnotationDeploymentScenarioGenerator(); + + @Override + public List<DeploymentDescription> generate(final TestClass testClass) { + final List<DeploymentDescription> stdDeploymentDescriptions = standard.generate(testClass); + + if (stdDeploymentDescriptions != null && !stdDeploymentDescriptions.isEmpty()) { + return stdDeploymentDescriptions; + } + + return Collections.singletonList(new DeploymentDescription("test.war", + ShrinkWrap.create(WebArchive.class, "test.war").add(EmptyAsset.INSTANCE, "WEB-INF/beans.xml"))); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tomee/blob/830b3729/tck/mp-jwt-embedded/src/test/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension ---------------------------------------------------------------------- diff --git a/tck/mp-jwt-embedded/src/test/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension b/tck/mp-jwt-embedded/src/test/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension new file mode 100644 index 0000000..98a4867 --- /dev/null +++ b/tck/mp-jwt-embedded/src/test/resources/META-INF/services/org.jboss.arquillian.core.spi.LoadableExtension @@ -0,0 +1 @@ +org.apache.tomee.microprofile.jwt.AppDeploymentExtension \ No newline at end of file http://git-wip-us.apache.org/repos/asf/tomee/blob/830b3729/tck/mp-jwt-embedded/src/test/resources/dev.xml ---------------------------------------------------------------------- diff --git a/tck/mp-jwt-embedded/src/test/resources/dev.xml b/tck/mp-jwt-embedded/src/test/resources/dev.xml index 16038c9..f6c4fbd 100644 --- a/tck/mp-jwt-embedded/src/test/resources/dev.xml +++ b/tck/mp-jwt-embedded/src/test/resources/dev.xml @@ -41,8 +41,9 @@ </run> </groups> <classes> + <!-- OK + --> <class name="org.eclipse.microprofile.jwt.tck.parsing.TokenValidationTest" /> - <!-- <class name="org.eclipse.microprofile.jwt.tck.util.TokenUtilsTest" /> <class name="org.eclipse.microprofile.jwt.tck.parsing.TestTokenClaimTypesTest" /> <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.UnsecuredPingTest" /> @@ -52,10 +53,11 @@ <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.ProviderInjectionTest" /> <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.RolesAllowedTest" /> <class name="org.eclipse.microprofile.jwt.tck.container.jaxrs.InvalidTokenTest" /> + <!-- KO --> </classes> </test> - <!-- + <test name="extended-tests" verbose="10"> <groups> <define name="extended-groups"> @@ -79,5 +81,5 @@ <class name="org.eclipse.microprofile.jwt.tck.container.servlet.ServletTest" /> </classes> </test> - --> + </suite>