Return-Path: X-Original-To: apmail-cxf-commits-archive@www.apache.org Delivered-To: apmail-cxf-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id B08571010F for ; Thu, 13 Mar 2014 15:52:09 +0000 (UTC) Received: (qmail 92696 invoked by uid 500); 13 Mar 2014 15:43:44 -0000 Delivered-To: apmail-cxf-commits-archive@cxf.apache.org Received: (qmail 92120 invoked by uid 500); 13 Mar 2014 15:43:03 -0000 Mailing-List: contact commits-help@cxf.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cxf.apache.org Delivered-To: mailing list commits@cxf.apache.org Received: (qmail 91873 invoked by uid 99); 13 Mar 2014 15:42:20 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Thu, 13 Mar 2014 15:42:20 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id EE33E944788; Thu, 13 Mar 2014 15:42:19 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: coheigea@apache.org To: commits@cxf.apache.org Date: Thu, 13 Mar 2014 15:42:19 -0000 Message-Id: <233a463a63f646c8a5e0df1ebe374edb@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [1/3] git commit: Moving Claims Interceptor to rt/security Repository: cxf Updated Branches: refs/heads/master e1f10bf7e -> 760e4bd32 Moving Claims Interceptor to rt/security Project: http://git-wip-us.apache.org/repos/asf/cxf/repo Commit: http://git-wip-us.apache.org/repos/asf/cxf/commit/6276add2 Tree: http://git-wip-us.apache.org/repos/asf/cxf/tree/6276add2 Diff: http://git-wip-us.apache.org/repos/asf/cxf/diff/6276add2 Branch: refs/heads/master Commit: 6276add292e682e77601bd2ec3d8e545e5618b13 Parents: e1f10bf Author: Colm O hEigeartaigh Authored: Thu Mar 13 11:53:50 2014 +0000 Committer: Colm O hEigeartaigh Committed: Thu Mar 13 15:42:10 2014 +0000 ---------------------------------------------------------------------- .../security/saml/authorization/ClaimBean.java | 53 ---- .../authorization/ClaimsAuthorizingFilter.java | 2 + .../ClaimsAuthorizingInterceptor.java | 243 --------------- .../ClaimsAuthorizingInterceptorTest.java | 297 ------------------- .../cxf/rt/security/claims/ClaimBean.java | 51 ++++ .../claims/ClaimsAuthorizingInterceptor.java | 242 +++++++++++++++ .../ClaimsAuthorizingInterceptorTest.java | 295 ++++++++++++++++++ 7 files changed, 590 insertions(+), 593 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cxf/blob/6276add2/rt/rs/security/xml/src/main/java/org/apache/cxf/rs/security/saml/authorization/ClaimBean.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/xml/src/main/java/org/apache/cxf/rs/security/saml/authorization/ClaimBean.java b/rt/rs/security/xml/src/main/java/org/apache/cxf/rs/security/saml/authorization/ClaimBean.java deleted file mode 100644 index 812bd6e..0000000 --- a/rt/rs/security/xml/src/main/java/org/apache/cxf/rs/security/saml/authorization/ClaimBean.java +++ /dev/null @@ -1,53 +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.cxf.rs.security.saml.authorization; - -import org.apache.cxf.rt.security.claims.SAMLClaim; -import org.apache.cxf.security.claims.authorization.ClaimMode; - - -public class ClaimBean { - private SAMLClaim claim; - private ClaimMode claimMode; - private boolean matchAll; - - public ClaimBean(SAMLClaim claim) { - this.claim = claim; - } - - public ClaimBean(SAMLClaim claim, - ClaimMode claimMode, - boolean matchAll) { - this.claim = claim; - this.claimMode = claimMode; - this.matchAll = matchAll; - } - - public SAMLClaim getClaim() { - return claim; - } - - public boolean isMatchAll() { - return matchAll; - } - - public ClaimMode getClaimMode() { - return claimMode; - } -} http://git-wip-us.apache.org/repos/asf/cxf/blob/6276add2/rt/rs/security/xml/src/main/java/org/apache/cxf/rs/security/saml/authorization/ClaimsAuthorizingFilter.java ---------------------------------------------------------------------- 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/saml/authorization/ClaimsAuthorizingFilter.java index 871da17..f373d91 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/saml/authorization/ClaimsAuthorizingFilter.java @@ -28,6 +28,8 @@ import javax.ws.rs.core.Response; import org.apache.cxf.interceptor.security.AccessDeniedException; import org.apache.cxf.jaxrs.utils.JAXRSUtils; import org.apache.cxf.message.Message; +import org.apache.cxf.rt.security.claims.ClaimBean; +import org.apache.cxf.rt.security.claims.ClaimsAuthorizingInterceptor; public class ClaimsAuthorizingFilter implements ContainerRequestFilter { http://git-wip-us.apache.org/repos/asf/cxf/blob/6276add2/rt/rs/security/xml/src/main/java/org/apache/cxf/rs/security/saml/authorization/ClaimsAuthorizingInterceptor.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/xml/src/main/java/org/apache/cxf/rs/security/saml/authorization/ClaimsAuthorizingInterceptor.java b/rt/rs/security/xml/src/main/java/org/apache/cxf/rs/security/saml/authorization/ClaimsAuthorizingInterceptor.java deleted file mode 100644 index ea83572..0000000 --- a/rt/rs/security/xml/src/main/java/org/apache/cxf/rs/security/saml/authorization/ClaimsAuthorizingInterceptor.java +++ /dev/null @@ -1,243 +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.cxf.rs.security.saml.authorization; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.logging.Logger; - -import org.apache.cxf.common.logging.LogUtils; -import org.apache.cxf.common.util.ClassHelper; -import org.apache.cxf.interceptor.Fault; -import org.apache.cxf.interceptor.security.AccessDeniedException; -import org.apache.cxf.message.Message; -import org.apache.cxf.phase.AbstractPhaseInterceptor; -import org.apache.cxf.phase.Phase; -import org.apache.cxf.rt.security.claims.SAMLClaim; -import org.apache.cxf.rt.security.saml.SAMLSecurityContext; -import org.apache.cxf.security.SecurityContext; -import org.apache.cxf.security.claims.authorization.Claim; -import org.apache.cxf.security.claims.authorization.ClaimMode; -import org.apache.cxf.security.claims.authorization.Claims; -import org.apache.cxf.service.Service; -import org.apache.cxf.service.invoker.MethodDispatcher; -import org.apache.cxf.service.model.BindingOperationInfo; - - -public class ClaimsAuthorizingInterceptor extends AbstractPhaseInterceptor { - - private static final Logger LOG = LogUtils.getL7dLogger(ClaimsAuthorizingInterceptor.class); - - private static final Set SKIP_METHODS; - static { - SKIP_METHODS = new HashSet(); - SKIP_METHODS.addAll(Arrays.asList( - new String[] {"wait", "notify", "notifyAll", - "equals", "toString", "hashCode"})); - } - - private Map> claims = new HashMap>(); - private Map nameAliases = Collections.emptyMap(); - private Map formatAliases = Collections.emptyMap(); - - public ClaimsAuthorizingInterceptor() { - super(Phase.PRE_INVOKE); - } - - public void handleMessage(Message message) throws Fault { - SecurityContext sc = message.get(SecurityContext.class); - if (!(sc instanceof SAMLSecurityContext)) { - throw new AccessDeniedException("Security Context is unavailable or unrecognized"); - } - - Method method = getTargetMethod(message); - - if (authorize((SAMLSecurityContext)sc, method)) { - return; - } - - throw new AccessDeniedException("Unauthorized"); - } - - public void setClaims(Map> claimsMap) { - claims.putAll(claimsMap); - } - - protected Method getTargetMethod(Message m) { - BindingOperationInfo bop = m.getExchange().get(BindingOperationInfo.class); - if (bop != null) { - MethodDispatcher md = (MethodDispatcher) - m.getExchange().get(Service.class).get(MethodDispatcher.class.getName()); - return md.getMethod(bop); - } - Method method = (Method)m.get("org.apache.cxf.resource.method"); - if (method != null) { - return method; - } - throw new AccessDeniedException("Method is not available : Unauthorized"); - } - - protected boolean authorize(SAMLSecurityContext sc, Method method) { - List list = claims.get(method.getName()); - org.apache.cxf.rt.security.claims.ClaimCollection actualClaims = sc.getClaims(); - - for (ClaimBean claimBean : list) { - org.apache.cxf.rt.security.claims.Claim claim = claimBean.getClaim(); - 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(((SAMLClaim)claim).getName()) - && ((SAMLClaim)cl).getNameFormat().equals(((SAMLClaim)claim).getNameFormat())) { - matchingClaim = cl; - break; - } - } - if (matchingClaim == null) { - if (claimBean.getClaimMode() == ClaimMode.STRICT) { - return false; - } else { - continue; - } - } - List claimValues = claim.getValues(); - List matchingClaimValues = matchingClaim.getValues(); - if (claimBean.isMatchAll() - && !matchingClaimValues.containsAll(claimValues)) { - return false; - } else { - boolean matched = false; - for (Object value : matchingClaimValues) { - if (claimValues.contains(value)) { - matched = true; - break; - } - } - if (!matched) { - return false; - } - } - } - return true; - } - - public void setSecuredObject(Object object) { - Class cls = ClassHelper.getRealClass(object); - findClaims(cls); - if (claims.isEmpty()) { - LOG.warning("The claims list is empty, the service object is not protected"); - } - } - - protected void findClaims(Class cls) { - if (cls == null || cls == Object.class) { - return; - } - List clsClaims = - getClaims(cls.getAnnotation(Claims.class), cls.getAnnotation(Claim.class)); - for (Method m : cls.getMethods()) { - if (SKIP_METHODS.contains(m.getName())) { - continue; - } - List methodClaims = - getClaims(m.getAnnotation(Claims.class), m.getAnnotation(Claim.class)); - - List allClaims = new ArrayList(methodClaims); - for (ClaimBean bean : clsClaims) { - if (isClaimOverridden(bean, methodClaims)) { - continue; - } - allClaims.add(bean); - } - - claims.put(m.getName(), allClaims); - } - if (!claims.isEmpty()) { - return; - } - - findClaims(cls.getSuperclass()); - - if (!claims.isEmpty()) { - return; - } - - for (Class interfaceCls : cls.getInterfaces()) { - findClaims(interfaceCls); - } - } - - private static boolean isClaimOverridden(ClaimBean bean, List mClaims) { - for (ClaimBean methodBean : mClaims) { - if (bean.getClaim().getName().equals(methodBean.getClaim().getName()) - && bean.getClaim().getNameFormat().equals(methodBean.getClaim().getNameFormat())) { - return true; - } - } - return false; - } - - private List getClaims( - Claims claimsAnn, Claim claimAnn) { - List claimsList = new ArrayList(); - - List annClaims = new ArrayList(); - if (claimsAnn != null) { - annClaims.addAll(Arrays.asList(claimsAnn.value())); - } else if (claimAnn != null) { - annClaims.add(claimAnn); - } - for (Claim ann : annClaims) { - SAMLClaim claim = new SAMLClaim(); - - String claimName = ann.name(); - if (nameAliases.containsKey(claimName)) { - claimName = nameAliases.get(claimName); - } - String claimFormat = ann.format(); - if (formatAliases.containsKey(claimFormat)) { - claimFormat = formatAliases.get(claimFormat); - } - - claim.setName(claimName); - claim.setNameFormat(claimFormat); - for (String value : ann.value()) { - claim.addValue(value); - } - - claimsList.add(new ClaimBean(claim, ann.mode(), ann.matchAll())); - } - return claimsList; - } - - public void setNameAliases(Map nameAliases) { - this.nameAliases = nameAliases; - } - - public void setFormatAliases(Map formatAliases) { - this.formatAliases = formatAliases; - } - -} http://git-wip-us.apache.org/repos/asf/cxf/blob/6276add2/rt/rs/security/xml/src/test/java/org/apache/cxf/rs/security/saml/authorization/ClaimsAuthorizingInterceptorTest.java ---------------------------------------------------------------------- diff --git a/rt/rs/security/xml/src/test/java/org/apache/cxf/rs/security/saml/authorization/ClaimsAuthorizingInterceptorTest.java b/rt/rs/security/xml/src/test/java/org/apache/cxf/rs/security/saml/authorization/ClaimsAuthorizingInterceptorTest.java deleted file mode 100644 index 9ebc050..0000000 --- a/rt/rs/security/xml/src/test/java/org/apache/cxf/rs/security/saml/authorization/ClaimsAuthorizingInterceptorTest.java +++ /dev/null @@ -1,297 +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.cxf.rs.security.saml.authorization; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.security.Principal; -import java.util.Arrays; -import java.util.Collections; -import java.util.Set; - -import org.apache.cxf.common.security.SimplePrincipal; -import org.apache.cxf.interceptor.security.AccessDeniedException; -import org.apache.cxf.interceptor.security.SecureAnnotationsInterceptor; -import org.apache.cxf.message.ExchangeImpl; -import org.apache.cxf.message.Message; -import org.apache.cxf.message.MessageImpl; -import org.apache.cxf.rt.security.claims.ClaimCollection; -import org.apache.cxf.rt.security.claims.SAMLClaim; -import org.apache.cxf.rt.security.saml.SAMLSecurityContext; -import org.apache.cxf.rt.security.saml.SAMLUtils; -import org.apache.cxf.security.SecurityContext; -import org.apache.cxf.security.claims.authorization.Claim; -import org.apache.cxf.security.claims.authorization.ClaimMode; -import org.apache.cxf.security.claims.authorization.Claims; -import org.apache.wss4j.common.saml.builder.SAML2Constants; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - - -public class ClaimsAuthorizingInterceptorTest extends Assert { - - private ClaimsAuthorizingInterceptor interceptor; - - @Before - public void setUp() { - interceptor = new ClaimsAuthorizingInterceptor(); - interceptor.setNameAliases( - Collections.singletonMap("authentication", "http://authentication")); - interceptor.setFormatAliases( - Collections.singletonMap("claims", "http://claims")); - interceptor.setSecuredObject(new TestService()); - - } - - @Test - public void testClaimDefaultNameAndFormat() throws Exception { - doTestClaims("claimWithDefaultNameAndFormat", - createDefaultClaim("admin", "user"), - createClaim("http://authentication", "http://claims", "password")); - try { - doTestClaims("claimWithDefaultNameAndFormat", - createDefaultClaim("user"), - createClaim("http://authentication", "http://claims", "password")); - fail("AccessDeniedException expected"); - } catch (AccessDeniedException ex) { - // expected - } - } - - @Test - public void testClaimMatchAll() throws Exception { - doTestClaims("claimMatchAll", - createDefaultClaim("admin", "manager"), - createClaim("http://authentication", "http://claims", "password")); - try { - doTestClaims("claimMatchAll", - createDefaultClaim("admin"), - createClaim("http://authentication", "http://claims", "password")); - doTestClaims("claimMatchAll", - createDefaultClaim("manager"), - createClaim("http://authentication", "http://claims", "password")); - fail("AccessDeniedException expected"); - } catch (AccessDeniedException ex) { - // expected - } - } - - @Test - public void testMissingExpectedClaim() throws Exception { - doTestClaims("claimWithDefaultNameAndFormat", - createDefaultClaim("admin"), - createClaim("http://authentication", "http://claims", "password")); - try { - doTestClaims("claimWithDefaultNameAndFormat", - createDefaultClaim("admin")); - fail("AccessDeniedException expected"); - } catch (AccessDeniedException ex) { - // expected - } - } - - @Test - public void testExtraNonExpectedClaim() throws Exception { - doTestClaims("claimWithDefaultNameAndFormat", - createDefaultClaim("admin", "user"), - createClaim("http://authentication", "http://claims", "password"), - createClaim("http://extra/claims", "http://claims", "claim")); - } - - @Test - public void testClaimSpecificNameAndFormat() throws Exception { - doTestClaims("claimWithSpecificNameAndFormat", - createClaim("http://cxf/roles", "http://claims", "admin", "user"), - createClaim("http://authentication", "http://claims", "password")); - try { - doTestClaims("claimWithSpecificNameAndFormat", - createDefaultClaim("admin", "user"), - createClaim("http://authentication", "http://claims", "password")); - fail("AccessDeniedException expected"); - } catch (AccessDeniedException ex) { - // expected - } - } - - @Test - public void testClaimLaxMode() throws Exception { - doTestClaims("claimLaxMode", - createClaim("http://authentication", "http://claims", "password")); - doTestClaims("claimLaxMode"); - try { - doTestClaims("claimLaxMode", - createClaim("http://authentication", "http://claims", "smartcard")); - fail("AccessDeniedException expected"); - } catch (AccessDeniedException ex) { - // expected - } - } - - @Test - public void testMultipleClaims() throws Exception { - doTestClaims("multipleClaims", - createDefaultClaim("admin"), - createClaim("http://authentication", "http://claims", "smartcard"), - createClaim("http://location", "http://claims", "UK")); - doTestClaims("multipleClaims", - createDefaultClaim("admin"), - createClaim("http://authentication", "http://claims", "password"), - createClaim("http://location", "http://claims", "USA")); - try { - doTestClaims("multipleClaims", - createDefaultClaim("admin"), - createClaim("http://authentication", "http://claims", "unsecuretransport"), - createClaim("http://location", "http://claims", "UK")); - fail("AccessDeniedException expected"); - } catch (AccessDeniedException ex) { - // expected - } - } - - @Test - public void testUserInRoleAndClaims() throws Exception { - SecureAnnotationsInterceptor in = new SecureAnnotationsInterceptor(); - in.setAnnotationClassName(SecureRole.class.getName()); - in.setSecuredObject(new TestService2()); - - Message m = prepareMessage(TestService2.class, "test", - createDefaultClaim("admin"), - createClaim("a", "b", "c")); - - in.handleMessage(m); - - ClaimsAuthorizingInterceptor in2 = new ClaimsAuthorizingInterceptor(); - org.apache.cxf.rt.security.claims.SAMLClaim claim = - new org.apache.cxf.rt.security.claims.SAMLClaim(); - claim.setNameFormat("a"); - claim.setName("b"); - claim.addValue("c"); - in2.setClaims(Collections.singletonMap("test", - Collections.singletonList( - new ClaimBean(claim)))); - in2.handleMessage(m); - - try { - in.handleMessage(prepareMessage(TestService2.class, "test", - createDefaultClaim("user"))); - fail("AccessDeniedException expected"); - } catch (AccessDeniedException ex) { - // expected - } - } - - - private void doTestClaims(String methodName, - org.apache.cxf.rt.security.claims.Claim... claim) - throws Exception { - Message m = prepareMessage(TestService.class, methodName, claim); - interceptor.handleMessage(m); - } - - private Message prepareMessage(Class cls, - String methodName, - org.apache.cxf.rt.security.claims.Claim... claim) - throws Exception { - ClaimCollection claims = new ClaimCollection(); - claims.addAll(Arrays.asList(claim)); - - Set roles = - SAMLUtils.parseRolesFromClaims(claims, SAMLClaim.SAML_ROLE_ATTRIBUTENAME_DEFAULT, - SAML2Constants.ATTRNAME_FORMAT_UNSPECIFIED); - - SecurityContext sc = new SAMLSecurityContext(new SimplePrincipal("user"), roles, claims); - Message m = new MessageImpl(); - m.setExchange(new ExchangeImpl()); - m.put(SecurityContext.class, sc); - m.put("org.apache.cxf.resource.method", - cls.getMethod(methodName, new Class[]{})); - return m; - } - - private org.apache.cxf.rt.security.claims.Claim createDefaultClaim( - Object... values) { - return createClaim(SAMLClaim.SAML_ROLE_ATTRIBUTENAME_DEFAULT, - SAML2Constants.ATTRNAME_FORMAT_UNSPECIFIED, - values); - } - - private org.apache.cxf.rt.security.claims.Claim createClaim( - String name, String format, Object... values) { - SAMLClaim claim = new SAMLClaim(); - claim.setName(name); - claim.setNameFormat(format); - claim.setValues(Arrays.asList(values)); - return claim; - } - - @Claim(name = "authentication", format = "claims", - value = "password") - public static class TestService { - // default name and format are used - @Claim({"admin", "manager" }) - public void claimWithDefaultNameAndFormat() { - - } - - // explicit name and format - @Claim(name = "http://cxf/roles", format = "http://claims", - value = {"admin", "manager" }) - public void claimWithSpecificNameAndFormat() { - - } - - @Claim(name = "http://authentication", format = "http://claims", - value = "password", mode = ClaimMode.LAX) - public void claimLaxMode() { - - } - - @Claims({ - @Claim(name = "http://location", format = "http://claims", - value = {"UK", "USA" }), - @Claim(value = {"admin", "manager" }), - @Claim(name = "authentication", format = "claims", - value = {"password", "smartcard" }) - }) - public void multipleClaims() { - - } - - // user must have both admin and manager roles, default is 'or' - @Claim(value = {"admin", "manager" }, - matchAll = true) - public void claimMatchAll() { - - } - } - public static class TestService2 { - @SecureRole("admin") - public void test() { - - } - } - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.RUNTIME) - public @interface SecureRole { - String[] value(); - } -} http://git-wip-us.apache.org/repos/asf/cxf/blob/6276add2/rt/security/src/main/java/org/apache/cxf/rt/security/claims/ClaimBean.java ---------------------------------------------------------------------- diff --git a/rt/security/src/main/java/org/apache/cxf/rt/security/claims/ClaimBean.java b/rt/security/src/main/java/org/apache/cxf/rt/security/claims/ClaimBean.java new file mode 100644 index 0000000..6c3908d --- /dev/null +++ b/rt/security/src/main/java/org/apache/cxf/rt/security/claims/ClaimBean.java @@ -0,0 +1,51 @@ +/** + * 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.cxf.rt.security.claims; + +import org.apache.cxf.security.claims.authorization.ClaimMode; + +public class ClaimBean { + private SAMLClaim claim; + private ClaimMode claimMode; + private boolean matchAll; + + public ClaimBean(SAMLClaim claim) { + this.claim = claim; + } + + public ClaimBean(SAMLClaim claim, + ClaimMode claimMode, + boolean matchAll) { + this.claim = claim; + this.claimMode = claimMode; + this.matchAll = matchAll; + } + + public SAMLClaim getClaim() { + return claim; + } + + public boolean isMatchAll() { + return matchAll; + } + + public ClaimMode getClaimMode() { + return claimMode; + } +} http://git-wip-us.apache.org/repos/asf/cxf/blob/6276add2/rt/security/src/main/java/org/apache/cxf/rt/security/claims/ClaimsAuthorizingInterceptor.java ---------------------------------------------------------------------- diff --git a/rt/security/src/main/java/org/apache/cxf/rt/security/claims/ClaimsAuthorizingInterceptor.java b/rt/security/src/main/java/org/apache/cxf/rt/security/claims/ClaimsAuthorizingInterceptor.java new file mode 100644 index 0000000..284b6ea --- /dev/null +++ b/rt/security/src/main/java/org/apache/cxf/rt/security/claims/ClaimsAuthorizingInterceptor.java @@ -0,0 +1,242 @@ +/** + * 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.cxf.rt.security.claims; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.logging.Logger; + +import org.apache.cxf.common.logging.LogUtils; +import org.apache.cxf.common.util.ClassHelper; +import org.apache.cxf.interceptor.Fault; +import org.apache.cxf.interceptor.security.AccessDeniedException; +import org.apache.cxf.message.Message; +import org.apache.cxf.phase.AbstractPhaseInterceptor; +import org.apache.cxf.phase.Phase; +import org.apache.cxf.rt.security.saml.SAMLSecurityContext; +import org.apache.cxf.security.SecurityContext; +import org.apache.cxf.security.claims.authorization.Claim; +import org.apache.cxf.security.claims.authorization.ClaimMode; +import org.apache.cxf.security.claims.authorization.Claims; +import org.apache.cxf.service.Service; +import org.apache.cxf.service.invoker.MethodDispatcher; +import org.apache.cxf.service.model.BindingOperationInfo; + + +public class ClaimsAuthorizingInterceptor extends AbstractPhaseInterceptor { + + private static final Logger LOG = LogUtils.getL7dLogger(ClaimsAuthorizingInterceptor.class); + + private static final Set SKIP_METHODS; + static { + SKIP_METHODS = new HashSet(); + SKIP_METHODS.addAll(Arrays.asList( + new String[] {"wait", "notify", "notifyAll", + "equals", "toString", "hashCode"})); + } + + private Map> claims = new HashMap>(); + private Map nameAliases = Collections.emptyMap(); + private Map formatAliases = Collections.emptyMap(); + + public ClaimsAuthorizingInterceptor() { + super(Phase.PRE_INVOKE); + } + + public void handleMessage(Message message) throws Fault { + SecurityContext sc = message.get(SecurityContext.class); + if (!(sc instanceof SAMLSecurityContext)) { + throw new AccessDeniedException("Security Context is unavailable or unrecognized"); + } + + Method method = getTargetMethod(message); + + if (authorize((SAMLSecurityContext)sc, method)) { + return; + } + + throw new AccessDeniedException("Unauthorized"); + } + + public void setClaims(Map> claimsMap) { + claims.putAll(claimsMap); + } + + protected Method getTargetMethod(Message m) { + BindingOperationInfo bop = m.getExchange().get(BindingOperationInfo.class); + if (bop != null) { + MethodDispatcher md = (MethodDispatcher) + m.getExchange().get(Service.class).get(MethodDispatcher.class.getName()); + return md.getMethod(bop); + } + Method method = (Method)m.get("org.apache.cxf.resource.method"); + if (method != null) { + return method; + } + throw new AccessDeniedException("Method is not available : Unauthorized"); + } + + protected boolean authorize(SAMLSecurityContext sc, Method method) { + List list = claims.get(method.getName()); + org.apache.cxf.rt.security.claims.ClaimCollection actualClaims = sc.getClaims(); + + for (ClaimBean claimBean : list) { + org.apache.cxf.rt.security.claims.Claim claim = claimBean.getClaim(); + 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(((SAMLClaim)claim).getName()) + && ((SAMLClaim)cl).getNameFormat().equals(((SAMLClaim)claim).getNameFormat())) { + matchingClaim = cl; + break; + } + } + if (matchingClaim == null) { + if (claimBean.getClaimMode() == ClaimMode.STRICT) { + return false; + } else { + continue; + } + } + List claimValues = claim.getValues(); + List matchingClaimValues = matchingClaim.getValues(); + if (claimBean.isMatchAll() + && !matchingClaimValues.containsAll(claimValues)) { + return false; + } else { + boolean matched = false; + for (Object value : matchingClaimValues) { + if (claimValues.contains(value)) { + matched = true; + break; + } + } + if (!matched) { + return false; + } + } + } + return true; + } + + public void setSecuredObject(Object object) { + Class cls = ClassHelper.getRealClass(object); + findClaims(cls); + if (claims.isEmpty()) { + LOG.warning("The claims list is empty, the service object is not protected"); + } + } + + protected void findClaims(Class cls) { + if (cls == null || cls == Object.class) { + return; + } + List clsClaims = + getClaims(cls.getAnnotation(Claims.class), cls.getAnnotation(Claim.class)); + for (Method m : cls.getMethods()) { + if (SKIP_METHODS.contains(m.getName())) { + continue; + } + List methodClaims = + getClaims(m.getAnnotation(Claims.class), m.getAnnotation(Claim.class)); + + List allClaims = new ArrayList(methodClaims); + for (ClaimBean bean : clsClaims) { + if (isClaimOverridden(bean, methodClaims)) { + continue; + } + allClaims.add(bean); + } + + claims.put(m.getName(), allClaims); + } + if (!claims.isEmpty()) { + return; + } + + findClaims(cls.getSuperclass()); + + if (!claims.isEmpty()) { + return; + } + + for (Class interfaceCls : cls.getInterfaces()) { + findClaims(interfaceCls); + } + } + + private static boolean isClaimOverridden(ClaimBean bean, List mClaims) { + for (ClaimBean methodBean : mClaims) { + if (bean.getClaim().getName().equals(methodBean.getClaim().getName()) + && bean.getClaim().getNameFormat().equals(methodBean.getClaim().getNameFormat())) { + return true; + } + } + return false; + } + + private List getClaims( + Claims claimsAnn, Claim claimAnn) { + List claimsList = new ArrayList(); + + List annClaims = new ArrayList(); + if (claimsAnn != null) { + annClaims.addAll(Arrays.asList(claimsAnn.value())); + } else if (claimAnn != null) { + annClaims.add(claimAnn); + } + for (Claim ann : annClaims) { + SAMLClaim claim = new SAMLClaim(); + + String claimName = ann.name(); + if (nameAliases.containsKey(claimName)) { + claimName = nameAliases.get(claimName); + } + String claimFormat = ann.format(); + if (formatAliases.containsKey(claimFormat)) { + claimFormat = formatAliases.get(claimFormat); + } + + claim.setName(claimName); + claim.setNameFormat(claimFormat); + for (String value : ann.value()) { + claim.addValue(value); + } + + claimsList.add(new ClaimBean(claim, ann.mode(), ann.matchAll())); + } + return claimsList; + } + + public void setNameAliases(Map nameAliases) { + this.nameAliases = nameAliases; + } + + public void setFormatAliases(Map formatAliases) { + this.formatAliases = formatAliases; + } + +} http://git-wip-us.apache.org/repos/asf/cxf/blob/6276add2/rt/security/src/test/java/org/apache/cxf/rt/security/claims/ClaimsAuthorizingInterceptorTest.java ---------------------------------------------------------------------- diff --git a/rt/security/src/test/java/org/apache/cxf/rt/security/claims/ClaimsAuthorizingInterceptorTest.java b/rt/security/src/test/java/org/apache/cxf/rt/security/claims/ClaimsAuthorizingInterceptorTest.java new file mode 100644 index 0000000..4d9e11d --- /dev/null +++ b/rt/security/src/test/java/org/apache/cxf/rt/security/claims/ClaimsAuthorizingInterceptorTest.java @@ -0,0 +1,295 @@ +/** + * 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.cxf.rt.security.claims; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.security.Principal; +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; + +import org.apache.cxf.common.security.SimplePrincipal; +import org.apache.cxf.interceptor.security.AccessDeniedException; +import org.apache.cxf.interceptor.security.SecureAnnotationsInterceptor; +import org.apache.cxf.message.ExchangeImpl; +import org.apache.cxf.message.Message; +import org.apache.cxf.message.MessageImpl; +import org.apache.cxf.rt.security.saml.SAMLSecurityContext; +import org.apache.cxf.rt.security.saml.SAMLUtils; +import org.apache.cxf.security.SecurityContext; +import org.apache.cxf.security.claims.authorization.Claim; +import org.apache.cxf.security.claims.authorization.ClaimMode; +import org.apache.cxf.security.claims.authorization.Claims; +import org.apache.wss4j.common.saml.builder.SAML2Constants; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + + +public class ClaimsAuthorizingInterceptorTest extends Assert { + + private ClaimsAuthorizingInterceptor interceptor; + + @Before + public void setUp() { + interceptor = new ClaimsAuthorizingInterceptor(); + interceptor.setNameAliases( + Collections.singletonMap("authentication", "http://authentication")); + interceptor.setFormatAliases( + Collections.singletonMap("claims", "http://claims")); + interceptor.setSecuredObject(new TestService()); + + } + + @Test + public void testClaimDefaultNameAndFormat() throws Exception { + doTestClaims("claimWithDefaultNameAndFormat", + createDefaultClaim("admin", "user"), + createClaim("http://authentication", "http://claims", "password")); + try { + doTestClaims("claimWithDefaultNameAndFormat", + createDefaultClaim("user"), + createClaim("http://authentication", "http://claims", "password")); + fail("AccessDeniedException expected"); + } catch (AccessDeniedException ex) { + // expected + } + } + + @Test + public void testClaimMatchAll() throws Exception { + doTestClaims("claimMatchAll", + createDefaultClaim("admin", "manager"), + createClaim("http://authentication", "http://claims", "password")); + try { + doTestClaims("claimMatchAll", + createDefaultClaim("admin"), + createClaim("http://authentication", "http://claims", "password")); + doTestClaims("claimMatchAll", + createDefaultClaim("manager"), + createClaim("http://authentication", "http://claims", "password")); + fail("AccessDeniedException expected"); + } catch (AccessDeniedException ex) { + // expected + } + } + + @Test + public void testMissingExpectedClaim() throws Exception { + doTestClaims("claimWithDefaultNameAndFormat", + createDefaultClaim("admin"), + createClaim("http://authentication", "http://claims", "password")); + try { + doTestClaims("claimWithDefaultNameAndFormat", + createDefaultClaim("admin")); + fail("AccessDeniedException expected"); + } catch (AccessDeniedException ex) { + // expected + } + } + + @Test + public void testExtraNonExpectedClaim() throws Exception { + doTestClaims("claimWithDefaultNameAndFormat", + createDefaultClaim("admin", "user"), + createClaim("http://authentication", "http://claims", "password"), + createClaim("http://extra/claims", "http://claims", "claim")); + } + + @Test + public void testClaimSpecificNameAndFormat() throws Exception { + doTestClaims("claimWithSpecificNameAndFormat", + createClaim("http://cxf/roles", "http://claims", "admin", "user"), + createClaim("http://authentication", "http://claims", "password")); + try { + doTestClaims("claimWithSpecificNameAndFormat", + createDefaultClaim("admin", "user"), + createClaim("http://authentication", "http://claims", "password")); + fail("AccessDeniedException expected"); + } catch (AccessDeniedException ex) { + // expected + } + } + + @Test + public void testClaimLaxMode() throws Exception { + doTestClaims("claimLaxMode", + createClaim("http://authentication", "http://claims", "password")); + doTestClaims("claimLaxMode"); + try { + doTestClaims("claimLaxMode", + createClaim("http://authentication", "http://claims", "smartcard")); + fail("AccessDeniedException expected"); + } catch (AccessDeniedException ex) { + // expected + } + } + + @Test + public void testMultipleClaims() throws Exception { + doTestClaims("multipleClaims", + createDefaultClaim("admin"), + createClaim("http://authentication", "http://claims", "smartcard"), + createClaim("http://location", "http://claims", "UK")); + doTestClaims("multipleClaims", + createDefaultClaim("admin"), + createClaim("http://authentication", "http://claims", "password"), + createClaim("http://location", "http://claims", "USA")); + try { + doTestClaims("multipleClaims", + createDefaultClaim("admin"), + createClaim("http://authentication", "http://claims", "unsecuretransport"), + createClaim("http://location", "http://claims", "UK")); + fail("AccessDeniedException expected"); + } catch (AccessDeniedException ex) { + // expected + } + } + + @Test + public void testUserInRoleAndClaims() throws Exception { + SecureAnnotationsInterceptor in = new SecureAnnotationsInterceptor(); + in.setAnnotationClassName(SecureRole.class.getName()); + in.setSecuredObject(new TestService2()); + + Message m = prepareMessage(TestService2.class, "test", + createDefaultClaim("admin"), + createClaim("a", "b", "c")); + + in.handleMessage(m); + + ClaimsAuthorizingInterceptor in2 = new ClaimsAuthorizingInterceptor(); + org.apache.cxf.rt.security.claims.SAMLClaim claim = + new org.apache.cxf.rt.security.claims.SAMLClaim(); + claim.setNameFormat("a"); + claim.setName("b"); + claim.addValue("c"); + in2.setClaims(Collections.singletonMap("test", + Collections.singletonList( + new ClaimBean(claim)))); + in2.handleMessage(m); + + try { + in.handleMessage(prepareMessage(TestService2.class, "test", + createDefaultClaim("user"))); + fail("AccessDeniedException expected"); + } catch (AccessDeniedException ex) { + // expected + } + } + + + private void doTestClaims(String methodName, + org.apache.cxf.rt.security.claims.Claim... claim) + throws Exception { + Message m = prepareMessage(TestService.class, methodName, claim); + interceptor.handleMessage(m); + } + + private Message prepareMessage(Class cls, + String methodName, + org.apache.cxf.rt.security.claims.Claim... claim) + throws Exception { + ClaimCollection claims = new ClaimCollection(); + claims.addAll(Arrays.asList(claim)); + + Set roles = + SAMLUtils.parseRolesFromClaims(claims, SAMLClaim.SAML_ROLE_ATTRIBUTENAME_DEFAULT, + SAML2Constants.ATTRNAME_FORMAT_UNSPECIFIED); + + SecurityContext sc = new SAMLSecurityContext(new SimplePrincipal("user"), roles, claims); + Message m = new MessageImpl(); + m.setExchange(new ExchangeImpl()); + m.put(SecurityContext.class, sc); + m.put("org.apache.cxf.resource.method", + cls.getMethod(methodName, new Class[]{})); + return m; + } + + private org.apache.cxf.rt.security.claims.Claim createDefaultClaim( + Object... values) { + return createClaim(SAMLClaim.SAML_ROLE_ATTRIBUTENAME_DEFAULT, + SAML2Constants.ATTRNAME_FORMAT_UNSPECIFIED, + values); + } + + private org.apache.cxf.rt.security.claims.Claim createClaim( + String name, String format, Object... values) { + SAMLClaim claim = new SAMLClaim(); + claim.setName(name); + claim.setNameFormat(format); + claim.setValues(Arrays.asList(values)); + return claim; + } + + @Claim(name = "authentication", format = "claims", + value = "password") + public static class TestService { + // default name and format are used + @Claim({"admin", "manager" }) + public void claimWithDefaultNameAndFormat() { + + } + + // explicit name and format + @Claim(name = "http://cxf/roles", format = "http://claims", + value = {"admin", "manager" }) + public void claimWithSpecificNameAndFormat() { + + } + + @Claim(name = "http://authentication", format = "http://claims", + value = "password", mode = ClaimMode.LAX) + public void claimLaxMode() { + + } + + @Claims({ + @Claim(name = "http://location", format = "http://claims", + value = {"UK", "USA" }), + @Claim(value = {"admin", "manager" }), + @Claim(name = "authentication", format = "claims", + value = {"password", "smartcard" }) + }) + public void multipleClaims() { + + } + + // user must have both admin and manager roles, default is 'or' + @Claim(value = {"admin", "manager" }, + matchAll = true) + public void claimMatchAll() { + + } + } + public static class TestService2 { + @SecureRole("admin") + public void test() { + + } + } + @Target(ElementType.METHOD) + @Retention(RetentionPolicy.RUNTIME) + public @interface SecureRole { + String[] value(); + } +}