Coverage Report - org.kuali.student.security.cxf.interceptors.SamlTokenCxfOutInterceptor
 
Classes in this File Line Coverage Branch Coverage Complexity
SamlTokenCxfOutInterceptor
0%
0/32
0%
0/4
2.6
SamlTokenCxfOutInterceptor$SamlTokenCxfOutInterceptorInternal
0%
0/92
0%
0/38
2.6
 
 1  
 /**
 2  
  * Copyright 2010 The Kuali Foundation Licensed under the
 3  
  * Educational Community License, Version 2.0 (the "License"); you may
 4  
  * not use this file except in compliance with the License. You may
 5  
  * obtain a copy of the License at
 6  
  *
 7  
  * http://www.osedu.org/licenses/ECL-2.0
 8  
  *
 9  
  * Unless required by applicable law or agreed to in writing,
 10  
  * software distributed under the License is distributed on an "AS IS"
 11  
  * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 12  
  * or implied. See the License for the specific language governing
 13  
  * permissions and limitations under the License.
 14  
  */
 15  
 
 16  
 package org.kuali.student.security.cxf.interceptors;
 17  
 
 18  
 import java.util.Collections;
 19  
 import java.util.Map;
 20  
 import java.util.Set;
 21  
 import java.util.Vector;
 22  
 import java.util.logging.Level;
 23  
 import java.util.logging.Logger;
 24  
 
 25  
 import javax.xml.soap.SOAPElement;
 26  
 import javax.xml.soap.SOAPEnvelope;
 27  
 import javax.xml.soap.SOAPException;
 28  
 import javax.xml.soap.SOAPFactory;
 29  
 import javax.xml.soap.SOAPHeader;
 30  
 import javax.xml.soap.SOAPMessage;
 31  
 import javax.xml.soap.SOAPPart;
 32  
 
 33  
 import org.apache.cxf.binding.soap.SoapFault;
 34  
 import org.apache.cxf.binding.soap.SoapMessage;
 35  
 import org.apache.cxf.binding.soap.SoapVersion;
 36  
 import org.apache.cxf.binding.soap.saaj.SAAJOutInterceptor;
 37  
 import org.apache.cxf.common.i18n.Message;
 38  
 import org.apache.cxf.common.logging.LogUtils;
 39  
 import org.apache.cxf.interceptor.Fault;
 40  
 import org.apache.cxf.phase.Phase;
 41  
 import org.apache.cxf.phase.PhaseInterceptor;
 42  
 import org.apache.cxf.ws.security.wss4j.AbstractWSS4JInterceptor;
 43  
 import org.apache.ws.security.WSConstants;
 44  
 import org.apache.ws.security.WSEncryptionPart;
 45  
 import org.apache.ws.security.WSSConfig;
 46  
 import org.apache.ws.security.WSSecurityException;
 47  
 import org.apache.ws.security.handler.RequestData;
 48  
 import org.apache.ws.security.handler.WSHandlerConstants;
 49  
 import org.apache.ws.security.util.WSSecurityUtil;
 50  
 import org.opensaml.SAMLAssertion;
 51  
 import org.opensaml.SAMLException;
 52  
 import org.w3c.dom.Document;
 53  
 import org.w3c.dom.Element;
 54  
 import org.w3c.dom.Node;
 55  
 
 56  
 /*  Most of this code was taken from org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor
 57  
     and modified for our purpose, additions should have comments starting with 'KS: '
 58  
     
 59  
     This WSS4JOutInterceptor along with the wss4j library, handled the sender-vouches with a signed 
 60  
     SAML Assertion correctly. The information to create that sender-vouches Assertion comes from the 
 61  
     saml.properties file.
 62  
     We needed to create another signed SAML Assertion that would hold the authenticated user and any 
 63  
     SAML attributes along with the original voucher SAML Assertion. In the end the message would have two
 64  
     signed SAML assertions. This was not possible with WSS4JOutInterceptor. see 'KS :' comments
 65  
     
 66  
 */
 67  0
 public class SamlTokenCxfOutInterceptor extends AbstractWSS4JInterceptor {
 68  0
     private static final Logger LOG = LogUtils
 69  
             .getL7dLogger(SamlTokenCxfOutInterceptor.class);
 70  
 
 71  0
     private static final Logger TIME_LOG = LogUtils
 72  
             .getL7dLogger(SamlTokenCxfOutInterceptor.class,
 73  
                           null,
 74  
                           SamlTokenCxfOutInterceptor.class.getName() + "-Time");
 75  
     private SamlTokenCxfOutInterceptorInternal ending;
 76  0
     private SAAJOutInterceptor saajOut = new SAAJOutInterceptor();
 77  
     private boolean mtomEnabled;
 78  
     
 79  
     // KS : added to hold information used to create authenticated user, SAML Assertion
 80  
     // KS : made this a ThreadLocal since we don't know if interceptors are thread-safe.
 81  
     //private Map<String,String> samlProperties = new HashMap<String,String>();
 82  0
     private ThreadLocal<Map<String,String>> samlPropertiesHolder = new ThreadLocal<Map<String,String>>();
 83  0
     private ThreadLocal<SAMLAssertion> samlAssertionHolder = new ThreadLocal<SAMLAssertion>();;
 84  
     
 85  
     public SamlTokenCxfOutInterceptor() {
 86  0
         super();
 87  0
         setPhase(Phase.PRE_PROTOCOL);
 88  0
         getAfter().add(SAAJOutInterceptor.class.getName());
 89  
         
 90  0
         ending = createEndingInterceptor();
 91  0
     }
 92  
     
 93  
     public SamlTokenCxfOutInterceptor(Map<String, Object> props) {
 94  0
         this();
 95  0
         setProperties(props);
 96  0
     }
 97  
     
 98  
     public boolean isAllowMTOM() {
 99  0
         return mtomEnabled;
 100  
     }
 101  
     /**
 102  
      * Enable or disable mtom with WS-Security.   By default MTOM is disabled as
 103  
      * attachments would not get encrypted or be part of the signature.
 104  
      * @param mtomEnabled
 105  
      */
 106  
     public void setAllowMTOM(boolean allowMTOM) {
 107  0
         this.mtomEnabled = allowMTOM;
 108  0
     }
 109  
 
 110  
     public void handleMessage(SoapMessage mc) throws Fault {
 111  
         //must turn off mtom when using WS-Sec so binary is inlined so it can
 112  
         //be properly signed/encrypted/etc...
 113  0
         if (!mtomEnabled) {
 114  0
             mc.put(org.apache.cxf.message.Message.MTOM_ENABLED, false);
 115  
         }
 116  
         
 117  0
         if (mc.getContent(SOAPMessage.class) == null) {
 118  0
             saajOut.handleMessage(mc);
 119  
         }
 120  
         
 121  0
         mc.getInterceptorChain().add(ending);
 122  0
     }    
 123  
     public void handleFault(SoapMessage message) {
 124  0
         saajOut.handleFault(message);
 125  0
     } 
 126  
     
 127  
     public final SamlTokenCxfOutInterceptorInternal createEndingInterceptor() {
 128  0
         return new SamlTokenCxfOutInterceptorInternal();
 129  
     }
 130  
     
 131  0
     final class SamlTokenCxfOutInterceptorInternal 
 132  
         implements PhaseInterceptor<SoapMessage> {
 133  0
         public SamlTokenCxfOutInterceptorInternal() {
 134  0
             super();
 135  0
         }
 136  
         
 137  
         public void handleMessage(SoapMessage mc) throws Fault {
 138  
             // KS : added so we can create the authenticated user SAML Assertion before the message is handled.
 139  0
             String wsuId = handleMessageUserSAML(mc);
 140  
             
 141  0
             boolean doDebug = LOG.isLoggable(Level.FINE);
 142  0
             boolean doTimeDebug = TIME_LOG.isLoggable(Level.FINE);
 143  0
             SoapVersion version = mc.getVersion();
 144  
     
 145  0
             long t0 = 0;
 146  0
             long t1 = 0;
 147  0
             long t2 = 0;
 148  
     
 149  0
             if (doTimeDebug) {
 150  0
                 t0 = System.currentTimeMillis();
 151  
             }
 152  
     
 153  0
             if (doDebug) {
 154  0
                 LOG.fine("SamlTokenCxfOutInterceptor: enter handleMessage()");
 155  
             }
 156  
             
 157  
             // KS : this has been the problem and why we needed to code our own WSS4JOutInterceptor.
 158  
             // WSS4JOutInterceptor needed to expose RequestData out of here so you can add the signature parts
 159  
             // see code below, before doSenderAction();  Essentially anything signature parts, gets singed by wss4j.
 160  0
             RequestData reqData = new RequestData();
 161  
             
 162  0
             reqData.setMsgContext(mc);
 163  
             
 164  
             /*
 165  
              * The overall try, just to have a finally at the end to perform some
 166  
              * housekeeping.
 167  
              */
 168  
             try {
 169  
                 /*
 170  
                  * Get the action first.
 171  
                  */
 172  0
                 Vector actions = new Vector();
 173  0
                 String action = getString(WSHandlerConstants.ACTION, mc);
 174  0
                 if (action == null) {
 175  0
                     throw new SoapFault(new Message("NO_ACTION", LOG), version
 176  
                             .getReceiver());
 177  
                 }
 178  
     
 179  0
                 int doAction = WSSecurityUtil.decodeAction(action, actions);
 180  0
                 if (doAction == WSConstants.NO_SECURITY) {
 181  
                     return;
 182  
                 }
 183  
     
 184  
                 /*
 185  
                  * For every action we need a username, so get this now. The
 186  
                  * username defined in the deployment descriptor takes precedence.
 187  
                  */
 188  0
                 reqData.setUsername((String) getOption(WSHandlerConstants.USER));
 189  0
                 if (reqData.getUsername() == null
 190  
                         || reqData.getUsername().equals("")) {
 191  0
                     String username = (String) getProperty(reqData.getMsgContext(),
 192  
                             WSHandlerConstants.USER);
 193  0
                     if (username != null) {
 194  0
                         reqData.setUsername(username);
 195  
                     }
 196  
                 }
 197  
     
 198  
                 /*
 199  
                  * Now we perform some set-up for UsernameToken and Signature
 200  
                  * functions. No need to do it for encryption only. Check if
 201  
                  * username is available and then get a passowrd.
 202  
                  */
 203  0
                 if ((doAction & (WSConstants.SIGN | WSConstants.UT | WSConstants.UT_SIGN)) != 0
 204  
                         && (reqData.getUsername() == null
 205  
                         || reqData.getUsername().equals(""))) {
 206  
                     /*
 207  
                      * We need a username - if none throw an SoapFault. For
 208  
                      * encryption there is a specific parameter to get a username.
 209  
                      */
 210  0
                     throw new SoapFault(new Message("NO_USERNAME", LOG), version
 211  
                             .getReceiver());
 212  
                 }
 213  0
                 if (doDebug) {
 214  0
                     LOG.fine("Action: " + doAction);
 215  0
                     LOG.fine("Actor: " + reqData.getActor());
 216  
                 }
 217  
                 /*
 218  
                  * Now get the SOAP part from the request message and convert it
 219  
                  * into a Document. This forces CXF to serialize the SOAP request
 220  
                  * into FORM_STRING. This string is converted into a document.
 221  
                  * During the FORM_STRING serialization CXF performs multi-ref of
 222  
                  * complex data types (if requested), generates and inserts
 223  
                  * references for attachements and so on. The resulting Document
 224  
                  * MUST be the complete and final SOAP request as CXF would send it
 225  
                  * over the wire. Therefore this must shall be the last (or only)
 226  
                  * handler in a chain. Now we can perform our security operations on
 227  
                  * this request.
 228  
                  */
 229  0
                 SOAPMessage saaj = mc.getContent(SOAPMessage.class);
 230  
     
 231  0
                 if (saaj == null) {
 232  0
                     LOG.warning("SAAJOutHandler must be enabled for WS-Security!");
 233  0
                     throw new SoapFault(new Message("NO_SAAJ_DOC", LOG), version
 234  
                             .getReceiver());
 235  
                 }
 236  
     
 237  0
                 Document doc = saaj.getSOAPPart();
 238  
                 /**
 239  
                  * There is nothing to send...Usually happens when the provider
 240  
                  * needs to send a HTTP 202 message (with no content)
 241  
                  */
 242  0
                 if (mc == null) {
 243  
                     return;
 244  
                 }
 245  
     
 246  0
                 if (doTimeDebug) {
 247  0
                     t1 = System.currentTimeMillis();
 248  
                 }
 249  
                 
 250  
                 // KS : any element represented by an encryptionPart and added to signatureParts will be signed.
 251  
                 // The wsuId is the SAML Assertion we created above in handleMessageUserSAML(mc).
 252  0
                 WSEncryptionPart encP = new WSEncryptionPart(wsuId);
 253  0
                 reqData.getSignatureParts().add(encP);
 254  
                 // KS : add the body, so it is also singed
 255  0
                 WSEncryptionPart encPBody = new WSEncryptionPart("Body", 
 256  
                         doc.getDocumentElement().getNamespaceURI(), "Content");
 257  0
                 reqData.getSignatureParts().add(encPBody);
 258  
                 
 259  
                 
 260  0
                 doSenderAction(doAction, doc, reqData, actions, Boolean.TRUE
 261  
                         .equals(getProperty(mc, org.apache.cxf.message.Message.REQUESTOR_ROLE)));
 262  
                 
 263  0
                 if (doTimeDebug) {
 264  0
                     t2 = System.currentTimeMillis();
 265  0
                     TIME_LOG.fine("Send request: total= " + (t2 - t0)
 266  
                             + " request preparation= " + (t1 - t0)
 267  
                             + " request processing= " + (t2 - t1)
 268  
                             + "\n");
 269  
                 }
 270  
     
 271  0
                 if (doDebug) {
 272  0
                     LOG.fine("SamlTokenCxfOutInterceptor: exit handleMessage()");
 273  
                 }
 274  0
             } catch (WSSecurityException e) {
 275  0
                 throw new SoapFault(new Message("SECURITY_FAILED", LOG), e, version
 276  
                         .getSender());
 277  
             } finally {
 278  0
                 reqData.clear();
 279  0
                 reqData = null;
 280  0
             }
 281  0
         }
 282  
         
 283  
         public Set<String> getAfter() {
 284  0
             return Collections.emptySet();
 285  
         }
 286  
 
 287  
         public Set<String> getBefore() {
 288  0
             return Collections.emptySet();
 289  
         }
 290  
 
 291  
         public String getId() {
 292  0
             return SamlTokenCxfOutInterceptorInternal.class.getName();
 293  
         }
 294  
 
 295  
         public String getPhase() {
 296  0
             return Phase.POST_PROTOCOL;
 297  
         }
 298  
 
 299  
         public void handleFault(SoapMessage message) {
 300  
             //nothing
 301  0
         }
 302  
         
 303  
         // KS : All three methods below added to create the authenticated user SAML Assertion
 304  
         public String handleMessageUserSAML(SoapMessage msg) throws Fault {
 305  0
             String wsuId = null;
 306  0
             Node assertionNode = null;
 307  
             
 308  
 /*            String user = getSamlProperties().get("user");
 309  
             String pgt = getSamlProperties().get("proxyGrantingTicket");
 310  
             String proxies = getSamlProperties().get("proxies");
 311  
             String issuer = getSamlProperties().get("samlIssuerForUser");
 312  
             String nameQualifier = getSamlProperties().get("samlIssuerForUser");
 313  
             
 314  
             SAMLAssertion assertion = new SAMLAssertion();
 315  
             assertion.setIssuer(issuer);
 316  
             
 317  
             try{   
 318  
                 // prepare subject
 319  
                 SAMLNameIdentifier nameId = new SAMLNameIdentifier(user, nameQualifier, "");
 320  
                 String[] confirmationMethods = {SAMLSubject.CONF_SENDER_VOUCHES};
 321  
                 SAMLSubject subject = new SAMLSubject();
 322  
                 subject.setNameIdentifier(nameId);
 323  
                 subject.setConfirmationMethods(Arrays.asList(confirmationMethods));
 324  
             
 325  
                 // prepare auth statement
 326  
                 SAMLAuthenticationStatement authStmt = new SAMLAuthenticationStatement();
 327  
                 authStmt.setAuthInstant(new Date());
 328  
                 authStmt.setAuthMethod(SAMLAuthenticationStatement.AuthenticationMethod_Password);
 329  
                 authStmt.setSubject(subject);
 330  
             
 331  
                 // prepare attributes
 332  
                 SAMLAttributeStatement attrStatement = new SAMLAttributeStatement();
 333  
                 SAMLAttribute attr1 = new SAMLAttribute();
 334  
                 attr1.setName("proxyGrantingTicket");
 335  
                 attr1.setNamespace("Namesapce_of_Attribute1");
 336  
             
 337  
                 SAMLAttribute attr2 = new SAMLAttribute();
 338  
                 attr2.setName("proxies");
 339  
                 attr2.setNamespace("Namesapce_of_Attribute2");
 340  
             
 341  
                 
 342  
                 attr1.addValue(pgt);
 343  
                 attr1.addValue("additional value for proxy granting ticket");
 344  
                 attr2.addValue(proxies);
 345  
                 attr2.addValue("additional value for proxies");
 346  
                 
 347  
                 attrStatement.addAttribute(attr1);
 348  
                 attrStatement.addAttribute(attr2);
 349  
                 
 350  
                 SAMLSubject subjectInAttr = (SAMLSubject)subject.clone();
 351  
                 attrStatement.setSubject(subjectInAttr);
 352  
                 
 353  
                 // prepare Assertion
 354  
                 assertion.addStatement(authStmt);
 355  
                 assertion.addStatement(attrStatement);
 356  
                 
 357  
                 assertionNode = assertion.toDOM();
 358  
                 
 359  
             } catch(SAMLException se){
 360  
                 Logger log = Logger.getLogger(SamlTokenCxfOutInterceptor.class.getName());
 361  
                 throw new Fault("Error when adding SAML Attributes or Attribute Statement : ", log, se);
 362  
             } catch(CloneNotSupportedException cnse){
 363  
                 Logger log = Logger.getLogger(SamlTokenCxfOutInterceptor.class.getName());
 364  
                 throw new Fault("Error when cloning subject : ", log, cnse);
 365  
             } */
 366  
             
 367  
             try{
 368  0
                 assertionNode = getSamlAssertion().toDOM();
 369  0
             } catch(SAMLException se){
 370  0
                 Logger log = Logger.getLogger(SamlTokenCxfOutInterceptor.class.getName());
 371  0
                 throw new Fault("Error when adding SAML Attributes or Attribute Statement : ", log, se);
 372  0
             }
 373  
             
 374  
             try{
 375  
                 // get the envelope and create a header
 376  0
                 SOAPMessage soapMsg = msg.getContent(SOAPMessage.class);
 377  0
                 SOAPPart doc = soapMsg.getSOAPPart();
 378  0
                 SOAPEnvelope envelope = doc.getEnvelope();
 379  0
                 envelope.addHeader();
 380  0
                 SOAPHeader soapHeader = soapMsg.getSOAPHeader();
 381  
                 
 382  
                 
 383  0
                 SOAPFactory soapFactory = SOAPFactory.newInstance();
 384  
                 
 385  
                 // create wsse:Security element
 386  0
                 SOAPElement wsseSecurity = soapFactory.createElement("Security", "wsse", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
 387  
                 
 388  
                 // insert assertion in wsse:Security element
 389  0
                 if(assertionNode.getNodeType() == Node.ELEMENT_NODE ){
 390  0
                     Element assertionElement = (Element)assertionNode;
 391  
                     
 392  
                     // add a wsu:Id
 393  0
                     wsuId = setWsuId(assertionElement);
 394  
                     
 395  0
                     SOAPElement assertionSOAP = soapFactory.createElement(assertionElement);
 396  0
                     wsseSecurity.addChildElement(assertionSOAP);
 397  
                 }
 398  
                 // insert wsse:Security in header element
 399  0
                 soapHeader.addChildElement(wsseSecurity);
 400  0
             } catch(SOAPException se){
 401  0
                 throw new Fault(se);
 402  0
             }
 403  0
             return wsuId;
 404  
         }
 405  
         
 406  
         protected String setWsuId(Element bodyElement) {
 407  0
             String id = bodyElement.getAttributeNS(WSConstants.WSU_NS, "Id");
 408  
 
 409  0
             if ((id == null) || (id.length() == 0)) {
 410  0
                 id = WSSConfig.getDefaultWSConfig().getIdAllocator().createId("id-", bodyElement);
 411  0
                 String prefix = 
 412  
                     WSSecurityUtil.setNamespace(bodyElement, WSConstants.WSU_NS, WSConstants.WSU_PREFIX);
 413  0
                 bodyElement.setAttributeNS(WSConstants.WSU_NS, prefix + ":Id", id);
 414  
             }
 415  0
             return id;
 416  
         }
 417  
     }
 418  
     
 419  
     public void setSamlProperties(Map<String, String> samlProperties){
 420  
         //this.samlProperties = samlProperties;
 421  0
         this.samlPropertiesHolder.set(samlProperties);
 422  0
     }
 423  
 
 424  
     public Map<String, String> getSamlProperties() {
 425  
         //return samlProperties;
 426  0
         return this.samlPropertiesHolder.get();
 427  
     }
 428  
 
 429  
     public SAMLAssertion getSamlAssertion() {
 430  0
         return this.samlAssertionHolder.get();
 431  
     }
 432  
 
 433  
     public void setSamlAssertion(SAMLAssertion samlAssertion) {
 434  0
         this.samlAssertionHolder.set(samlAssertion);
 435  0
     }
 436  
 }