This is an automated email from the ASF dual-hosted git repository.

coheigea pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cxf.git


The following commit(s) were added to refs/heads/master by this push:
     new d3de21c4 CXF-6727 - Make Claim based access control 
interceptors/annotations usable with JWT tokens
d3de21c4 is described below

commit d3de21c45a8f6c74996b5edecfe2e03f8b6a2bd3
Author: Colm O hEigeartaigh <cohei...@apache.org>
AuthorDate: Thu Sep 13 12:24:25 2018 +0100

    CXF-6727 - Make Claim based access control interceptors/annotations usable 
with JWT tokens
---
 .../jose/jaxrs/JwtTokenSecurityContext.java        |  26 ++++-
 .../ClaimsAuthorizingFilter.java                   |   2 +-
 .../interceptor/ClaimsAuthorizingInterceptor.java  |  11 +-
 .../ClaimsAuthorizingInterceptorTest.java          |  71 ++++++++++--
 .../jaxrs/security/jose/jwt/BookStoreAuthn.java    |  15 +++
 .../jaxrs/security/jose/jwt/JWTAuthnAuthzTest.java | 121 +++++++++++++++++++++
 .../jaxrs/security/jose/jwt/authn-authz-server.xml |   6 +
 .../systest/jaxrs/security/saml/secureServer.xml   |   2 +-
 8 files changed, 235 insertions(+), 19 deletions(-)

diff --git 
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwtTokenSecurityContext.java
 
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwtTokenSecurityContext.java
index a72b902..0630d10 100644
--- 
a/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwtTokenSecurityContext.java
+++ 
b/rt/rs/security/jose-parent/jose-jaxrs/src/main/java/org/apache/cxf/rs/security/jose/jaxrs/JwtTokenSecurityContext.java
@@ -21,19 +21,24 @@ package org.apache.cxf.rs.security.jose.jaxrs;
 import java.security.Principal;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 import javax.security.auth.Subject;
 
 import org.apache.cxf.common.security.SimpleGroup;
 import org.apache.cxf.common.security.SimplePrincipal;
+import org.apache.cxf.helpers.CastUtils;
 import org.apache.cxf.rs.security.jose.jwt.JwtToken;
-import org.apache.cxf.security.LoginSecurityContext;
+import org.apache.cxf.rt.security.claims.Claim;
+import org.apache.cxf.rt.security.claims.ClaimCollection;
+import org.apache.cxf.rt.security.claims.ClaimsSecurityContext;
 
-public class JwtTokenSecurityContext implements LoginSecurityContext {
+public class JwtTokenSecurityContext implements ClaimsSecurityContext {
     private final JwtToken token;
     private final Principal principal;
     private final Set<Principal> roles;
+    private final ClaimCollection claims = new ClaimCollection();
 
     public JwtTokenSecurityContext(JwtToken jwt, String roleClaim) {
         principal = new SimplePrincipal(jwt.getClaims().getSubject());
@@ -47,6 +52,18 @@ public class JwtTokenSecurityContext implements 
LoginSecurityContext {
         } else {
             roles = Collections.emptySet();
         }
+
+        // Parse JwtToken into ClaimCollection
+        jwt.getClaims().asMap().forEach((String name, Object values) -> {
+            Claim claim = new Claim();
+            claim.setClaimType(name);
+            if (values instanceof List<?>) {
+                claim.setValues(CastUtils.cast((List<?>)values));
+            } else {
+                claim.setValues(Collections.singletonList(values));
+            }
+            claims.add(claim);
+        });
     }
 
     public JwtToken getToken() {
@@ -78,4 +95,9 @@ public class JwtTokenSecurityContext implements 
LoginSecurityContext {
         return false;
     }
 
+    @Override
+    public ClaimCollection getClaims() {
+        return claims;
+    }
+
 }
diff --git 
a/rt/rs/security/xml/src/main/java/org/apache/cxf/rs/security/saml/authorization/ClaimsAuthorizingFilter.java
 
b/rt/rs/security/xml/src/main/java/org/apache/cxf/rs/security/claims/ClaimsAuthorizingFilter.java
similarity index 97%
rename from 
rt/rs/security/xml/src/main/java/org/apache/cxf/rs/security/saml/authorization/ClaimsAuthorizingFilter.java
rename to 
rt/rs/security/xml/src/main/java/org/apache/cxf/rs/security/claims/ClaimsAuthorizingFilter.java
index 45112e4..9d5dfcd 100644
--- 
a/rt/rs/security/xml/src/main/java/org/apache/cxf/rs/security/saml/authorization/ClaimsAuthorizingFilter.java
+++ 
b/rt/rs/security/xml/src/main/java/org/apache/cxf/rs/security/claims/ClaimsAuthorizingFilter.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.cxf.rs.security.saml.authorization;
+package org.apache.cxf.rs.security.claims;
 
 import java.util.List;
 import java.util.Map;
diff --git 
a/rt/security/src/main/java/org/apache/cxf/rt/security/claims/interceptor/ClaimsAuthorizingInterceptor.java
 
b/rt/security/src/main/java/org/apache/cxf/rt/security/claims/interceptor/ClaimsAuthorizingInterceptor.java
index 8f254be..5bcb3c0 100644
--- 
a/rt/security/src/main/java/org/apache/cxf/rt/security/claims/interceptor/ClaimsAuthorizingInterceptor.java
+++ 
b/rt/security/src/main/java/org/apache/cxf/rt/security/claims/interceptor/ClaimsAuthorizingInterceptor.java
@@ -107,9 +107,14 @@ public class ClaimsAuthorizingInterceptor extends 
AbstractPhaseInterceptor<Messa
         for (ClaimBean claimBean : list) {
             org.apache.cxf.rt.security.claims.Claim matchingClaim = null;
             for (org.apache.cxf.rt.security.claims.Claim cl : actualClaims) {
-                if (cl instanceof SAMLClaim
-                    && 
((SAMLClaim)cl).getName().equals(claimBean.getClaim().getClaimType())
-                    && 
((SAMLClaim)cl).getNameFormat().equals(claimBean.getClaimFormat())) {
+                if (cl instanceof SAMLClaim) {
+                    // If it's a SAMLClaim the name + nameformat must match 
what's configured
+                    if 
(((SAMLClaim)cl).getName().equals(claimBean.getClaim().getClaimType())
+                        && 
((SAMLClaim)cl).getNameFormat().equals(claimBean.getClaimFormat())) {
+                        matchingClaim = cl;
+                        break;
+                    }
+                } else if 
(cl.getClaimType().equals(claimBean.getClaim().getClaimType())) {
                     matchingClaim = cl;
                     break;
                 }
diff --git 
a/rt/security/src/test/java/org/apache/cxf/rt/security/claims/interceptor/ClaimsAuthorizingInterceptorTest.java
 
b/rt/security/src/test/java/org/apache/cxf/rt/security/claims/interceptor/ClaimsAuthorizingInterceptorTest.java
index aadc168..c2ae08c 100644
--- 
a/rt/security/src/test/java/org/apache/cxf/rt/security/claims/interceptor/ClaimsAuthorizingInterceptorTest.java
+++ 
b/rt/security/src/test/java/org/apache/cxf/rt/security/claims/interceptor/ClaimsAuthorizingInterceptorTest.java
@@ -26,6 +26,7 @@ import java.security.Principal;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 import javax.security.auth.Subject;
@@ -69,12 +70,34 @@ public class ClaimsAuthorizingInterceptorTest extends 
Assert {
     @Test
     public void testClaimDefaultNameAndFormat() throws Exception {
         doTestClaims("claimWithDefaultNameAndFormat",
-                createDefaultClaim("admin", "user"),
-                createClaim("http://authentication";, "http://claims";, 
"password"));
+                     createDefaultClaim("admin", "user"),
+                     createClaim("http://authentication";, "http://claims";, 
"password"));
         try {
             doTestClaims("claimWithDefaultNameAndFormat",
-                    createDefaultClaim("user"),
-                    createClaim("http://authentication";, "http://claims";, 
"password"));
+                         createDefaultClaim("user"),
+                         createClaim("http://authentication";, "http://claims";, 
"password"));
+            fail("AccessDeniedException expected");
+        } catch (AccessDeniedException ex) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testNonSAMLClaimDefaultNameAndFormat() throws Exception {
+        org.apache.cxf.rt.security.claims.Claim claim1 = new 
org.apache.cxf.rt.security.claims.Claim();
+        claim1.setClaimType("role");
+        claim1.setValues(Arrays.asList("admin", "user"));
+        org.apache.cxf.rt.security.claims.Claim claim2 = new 
org.apache.cxf.rt.security.claims.Claim();
+        claim2.setClaimType("http://authentication";);
+        claim2.setValues(Arrays.asList("password"));
+
+        Message m = prepareMessage(TestService.class, "claimWithSpecificName", 
"role", claim1, claim2);
+        interceptor.handleMessage(m);
+
+        try {
+            claim1.setValues(Arrays.asList("user"));
+            m = prepareMessage(TestService.class, "claimWithSpecificName", 
"role", claim1, claim2);
+            interceptor.handleMessage(m);
             fail("AccessDeniedException expected");
         } catch (AccessDeniedException ex) {
             // expected
@@ -202,7 +225,6 @@ public class ClaimsAuthorizingInterceptorTest extends 
Assert {
         }
     }
 
-
     private void doTestClaims(String methodName,
             org.apache.cxf.rt.security.claims.Claim... claim)
         throws Exception {
@@ -214,11 +236,19 @@ public class ClaimsAuthorizingInterceptorTest extends 
Assert {
             String methodName,
             org.apache.cxf.rt.security.claims.Claim... claim)
         throws Exception {
+        return prepareMessage(cls, methodName, 
SAMLClaim.SAML_ROLE_ATTRIBUTENAME_DEFAULT, claim);
+    }
+
+    private Message prepareMessage(Class<?> cls,
+                                   String methodName,
+                                   String roleName,
+                                   org.apache.cxf.rt.security.claims.Claim... 
claim)
+                               throws Exception {
         ClaimCollection claims = new ClaimCollection();
         claims.addAll(Arrays.asList(claim));
 
         Set<Principal> roles =
-            parseRolesFromClaims(claims, 
SAMLClaim.SAML_ROLE_ATTRIBUTENAME_DEFAULT,
+            parseRolesFromClaims(claims, roleName,
                                  
"urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified");
 
         ClaimsSecurityContext sc = new ClaimsSecurityContext() {
@@ -299,6 +329,12 @@ public class ClaimsAuthorizingInterceptorTest extends 
Assert {
 
         }
 
+        // explicit name
+        @Claim(name = "role", value = {"admin", "manager" })
+        public void claimWithSpecificName() {
+
+        }
+
         @Claim(name = "http://authentication";, format = "http://claims";,
                value = "password", mode = ClaimMode.LAX)
         public void claimLaxMode() {
@@ -348,14 +384,17 @@ public class ClaimsAuthorizingInterceptorTest extends 
Assert {
         Set<Principal> roles = new HashSet<>();
 
         for (org.apache.cxf.rt.security.claims.Claim claim : claims) {
-            if (claim instanceof SAMLClaim && 
((SAMLClaim)claim).getName().equals(name)
-                && (nameFormat == null
-                || nameFormat.equals(((SAMLClaim)claim).getNameFormat()))) {
-                for (Object claimValue : claim.getValues()) {
-                    if (claimValue instanceof String) {
-                        roles.add(new SimpleGroup((String)claimValue));
+            if (claim instanceof SAMLClaim) {
+                if (((SAMLClaim)claim).getName().equals(roleAttributeName)
+                    && (nameFormat == null || 
nameFormat.equals(((SAMLClaim)claim).getNameFormat()))) {
+                    addValues(roles, claim.getValues());
+                    if (claim.getValues().size() > 1) {
+                        // Don't search for other attributes with the same 
name if > 1 claim value
+                        break;
                     }
                 }
+            } else if (claim.getClaimType().equals(roleAttributeName)) {
+                addValues(roles, claim.getValues());
                 if (claim.getValues().size() > 1) {
                     // Don't search for other attributes with the same name if 
> 1 claim value
                     break;
@@ -365,4 +404,12 @@ public class ClaimsAuthorizingInterceptorTest extends 
Assert {
 
         return roles;
     }
+
+    private static void addValues(Set<Principal> roles, List<Object> values) {
+        for (Object claimValue : values) {
+            if (claimValue instanceof String) {
+                roles.add(new SimpleGroup((String)claimValue));
+            }
+        }
+    }
 }
diff --git 
a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jose/jwt/BookStoreAuthn.java
 
b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jose/jwt/BookStoreAuthn.java
index 29cb330..6954c5c 100644
--- 
a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jose/jwt/BookStoreAuthn.java
+++ 
b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jose/jwt/BookStoreAuthn.java
@@ -27,6 +27,8 @@ import javax.ws.rs.Produces;
 import javax.ws.rs.core.Context;
 
 import org.apache.cxf.jaxrs.ext.MessageContext;
+import org.apache.cxf.security.claims.authorization.Claim;
+import org.apache.cxf.security.claims.authorization.Claims;
 import org.apache.cxf.systest.jaxrs.security.Book;
 
 import org.junit.Assert;
@@ -67,6 +69,19 @@ public class BookStoreAuthn {
         return book;
     }
 
+    @POST
+    @Path("/booksclaims")
+    @Produces("application/json")
+    @Consumes("application/json")
+    @Claims({
+        @Claim(name = "http://claims/authentication";,
+               value = {"fingertip", "smartcard" })
+    })
+    public Book echoBook3(Book book) {
+        checkAuthentication();
+        return book;
+    }
+
     private void checkAuthentication() {
         // Check that we have an authenticated principal
         
Assert.assertNotNull(jaxrsContext.getSecurityContext().getUserPrincipal());
diff --git 
a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jose/jwt/JWTAuthnAuthzTest.java
 
b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jose/jwt/JWTAuthnAuthzTest.java
index 50367e7..3a2531f 100644
--- 
a/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jose/jwt/JWTAuthnAuthzTest.java
+++ 
b/systests/rs-security/src/test/java/org/apache/cxf/systest/jaxrs/security/jose/jwt/JWTAuthnAuthzTest.java
@@ -245,6 +245,127 @@ public class JWTAuthnAuthzTest extends 
AbstractBusClientServerTestBase {
         assertNotEquals(response.getStatus(), 200);
     }
 
+    @org.junit.Test
+    public void testClaimsAuthorization() throws Exception {
+
+        URL busFile = JWTAuthnAuthzTest.class.getResource("client.xml");
+
+        List<Object> providers = new ArrayList<>();
+        providers.add(new JacksonJsonProvider());
+        providers.add(new JwtAuthenticationClientFilter());
+
+        String address = "https://localhost:"; + PORT + 
"/signedjwtauthz/bookstore/booksclaims";
+        WebClient client =
+            WebClient.create(address, providers, busFile.toString());
+        client.type("application/json").accept("application/json");
+
+        // Create the JWT Token
+        JwtClaims claims = new JwtClaims();
+        claims.setSubject("alice");
+        claims.setIssuer("DoubleItSTSIssuer");
+        claims.setIssuedAt(Instant.now().getEpochSecond());
+        claims.setAudiences(toList(address));
+        // The endpoint requires a role of "boss"
+        claims.setProperty("role", "boss");
+        // We also require a "smartcard" claim
+        claims.setProperty("http://claims/authentication";, "smartcard");
+
+        JwtToken token = new JwtToken(claims);
+
+        Map<String, Object> properties = new HashMap<>();
+        properties.put("rs.security.keystore.type", "jwk");
+        properties.put("rs.security.keystore.alias", "2011-04-29");
+        properties.put("rs.security.keystore.file",
+                       
"org/apache/cxf/systest/jaxrs/security/certs/jwkPrivateSet.txt");
+        properties.put("rs.security.signature.algorithm", "RS256");
+        properties.put(JwtConstants.JWT_TOKEN, token);
+        WebClient.getConfig(client).getRequestContext().putAll(properties);
+
+        Response response = client.post(new Book("book", 123L));
+        assertEquals(response.getStatus(), 200);
+
+        Book returnedBook = response.readEntity(Book.class);
+        assertEquals(returnedBook.getName(), "book");
+        assertEquals(returnedBook.getId(), 123L);
+    }
+
+    @org.junit.Test
+    public void testClaimsAuthorizationWeakClaims() throws Exception {
+
+        URL busFile = JWTAuthnAuthzTest.class.getResource("client.xml");
+
+        List<Object> providers = new ArrayList<>();
+        providers.add(new JacksonJsonProvider());
+        providers.add(new JwtAuthenticationClientFilter());
+
+        String address = "https://localhost:"; + PORT + 
"/signedjwtauthz/bookstore/booksclaims";
+        WebClient client =
+            WebClient.create(address, providers, busFile.toString());
+        client.type("application/json").accept("application/json");
+
+        // Create the JWT Token
+        JwtClaims claims = new JwtClaims();
+        claims.setSubject("alice");
+        claims.setIssuer("DoubleItSTSIssuer");
+        claims.setIssuedAt(Instant.now().getEpochSecond());
+        claims.setAudiences(toList(address));
+        // The endpoint requires a role of "boss"
+        claims.setProperty("role", "boss");
+        claims.setProperty("http://claims/authentication";, "password");
+
+        JwtToken token = new JwtToken(claims);
+
+        Map<String, Object> properties = new HashMap<>();
+        properties.put("rs.security.keystore.type", "jwk");
+        properties.put("rs.security.keystore.alias", "2011-04-29");
+        properties.put("rs.security.keystore.file",
+                       
"org/apache/cxf/systest/jaxrs/security/certs/jwkPrivateSet.txt");
+        properties.put("rs.security.signature.algorithm", "RS256");
+        properties.put(JwtConstants.JWT_TOKEN, token);
+        WebClient.getConfig(client).getRequestContext().putAll(properties);
+
+        Response response = client.post(new Book("book", 123L));
+        assertEquals(response.getStatus(), 403);
+    }
+
+    @org.junit.Test
+    public void testClaimsAuthorizationNoClaims() throws Exception {
+
+        URL busFile = JWTAuthnAuthzTest.class.getResource("client.xml");
+
+        List<Object> providers = new ArrayList<>();
+        providers.add(new JacksonJsonProvider());
+        providers.add(new JwtAuthenticationClientFilter());
+
+        String address = "https://localhost:"; + PORT + 
"/signedjwtauthz/bookstore/booksclaims";
+        WebClient client =
+            WebClient.create(address, providers, busFile.toString());
+        client.type("application/json").accept("application/json");
+
+        // Create the JWT Token
+        JwtClaims claims = new JwtClaims();
+        claims.setSubject("alice");
+        claims.setIssuer("DoubleItSTSIssuer");
+        claims.setIssuedAt(Instant.now().getEpochSecond());
+        claims.setAudiences(toList(address));
+        // The endpoint requires a role of "boss"
+        claims.setProperty("role", "boss");
+
+        JwtToken token = new JwtToken(claims);
+
+        Map<String, Object> properties = new HashMap<>();
+        properties.put("rs.security.keystore.type", "jwk");
+        properties.put("rs.security.keystore.alias", "2011-04-29");
+        properties.put("rs.security.keystore.file",
+                       
"org/apache/cxf/systest/jaxrs/security/certs/jwkPrivateSet.txt");
+        properties.put("rs.security.signature.algorithm", "RS256");
+        properties.put(JwtConstants.JWT_TOKEN, token);
+        WebClient.getConfig(client).getRequestContext().putAll(properties);
+
+        Response response = client.post(new Book("book", 123L));
+        assertEquals(response.getStatus(), 403);
+    }
+
     private List<String> toList(String address) {
         return Collections.singletonList(address);
     }
diff --git 
a/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/jose/jwt/authn-authz-server.xml
 
b/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/jose/jwt/authn-authz-server.xml
index 14cfa47..3c6a049 100644
--- 
a/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/jose/jwt/authn-authz-server.xml
+++ 
b/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/jose/jwt/authn-authz-server.xml
@@ -65,17 +65,23 @@ under the License.
            <map>
                <entry key="echoBook" value="boss"/>
                <entry key="echoBook2" value="boss"/>
+               <entry key="echoBook3" value="boss"/>
                <entry key="echoText" value="boss"/>
            </map>
        </property> 
     </bean>
     
+    <bean id="claimsHandler" 
class="org.apache.cxf.rs.security.claims.ClaimsAuthorizingFilter">
+        <property name="securedObject" ref="serviceBean"/>
+    </bean>
+    
     <jaxrs:server 
address="https://localhost:${testutil.ports.jaxrs-jwt-authn-authz}/signedjwtauthz";>
         <jaxrs:serviceBeans>
             <ref bean="serviceBean"/>
         </jaxrs:serviceBeans>
         <jaxrs:providers>
             <ref bean="jwtAuthzFilter"/>
+            <ref bean="claimsHandler"/>
         </jaxrs:providers>
         <jaxrs:inInterceptors>
             <ref bean="authorizationInterceptor"/>
diff --git 
a/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/saml/secureServer.xml
 
b/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/saml/secureServer.xml
index ebc44c5..fd9620d 100644
--- 
a/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/saml/secureServer.xml
+++ 
b/systests/rs-security/src/test/resources/org/apache/cxf/systest/jaxrs/security/saml/secureServer.xml
@@ -40,7 +40,7 @@ under the License.
     <bean id="serviceBean" 
class="org.apache.cxf.systest.jaxrs.security.saml.SecureBookStore"/>
     <bean id="serviceBeanClaims" 
class="org.apache.cxf.systest.jaxrs.security.saml.SecureClaimBookStore"/>
     <bean id="samlEnvHandler" 
class="org.apache.cxf.rs.security.saml.SamlEnvelopedInHandler"/>
-    <bean id="claimsHandler" 
class="org.apache.cxf.rs.security.saml.authorization.ClaimsAuthorizingFilter">
+    <bean id="claimsHandler" 
class="org.apache.cxf.rs.security.claims.ClaimsAuthorizingFilter">
         <property name="securedObject" ref="serviceBeanClaims"/>
     </bean>
     <bean id="authorizationInterceptor" 
class="org.apache.cxf.interceptor.security.SecureAnnotationsInterceptor">

Reply via email to