001/** 002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 003 * 004 * Copyright (c) 2006 Sun Microsystems Inc. All Rights Reserved 005 * 006 * The contents of this file are subject to the terms 007 * of the Common Development and Distribution License 008 * (the License). You may not use this file except in 009 * compliance with the License. 010 * 011 * You can obtain a copy of the License at 012 * https://opensso.dev.java.net/public/CDDLv1.0.html or 013 * opensso/legal/CDDLv1.0.txt 014 * See the License for the specific language governing 015 * permission and limitations under the License. 016 * 017 * When distributing Covered Code, include this CDDL 018 * Header Notice in each file and include the License file 019 * at opensso/legal/CDDLv1.0.txt. 020 * If applicable, add the following below the CDDL Header, 021 * with the fields enclosed by brackets [] replaced by 022 * your own identifying information: 023 * "Portions Copyrighted [year] [name of copyright owner]" 024 * 025 * $Id: FSResponse.java,v 1.2 2008/06/25 05:46:45 qcheng Exp $ 026 * 027 */ 028 029package com.sun.identity.federation.message; 030 031import java.text.ParseException; 032import java.util.List; 033import java.util.Collections; 034import java.util.ArrayList; 035import java.util.Iterator; 036 037import java.io.ByteArrayInputStream; 038import java.io.IOException; 039 040import org.w3c.dom.Element; 041import org.w3c.dom.Node; 042import org.w3c.dom.NodeList; 043import org.w3c.dom.Document; 044 045import com.sun.identity.shared.encode.Base64; 046import com.sun.identity.shared.xml.XMLUtils; 047import com.sun.identity.shared.DateUtils; 048import com.sun.identity.saml.common.SAMLConstants; 049import com.sun.identity.saml.common.SAMLException; 050import com.sun.identity.saml.common.SAMLResponderException; 051import com.sun.identity.saml.common.SAMLVersionMismatchException; 052import com.sun.identity.saml.xmlsig.XMLSignatureManager; 053import com.sun.identity.saml.protocol.Response; 054import com.sun.identity.saml.protocol.Status; 055 056import com.sun.identity.federation.message.common.FSMsgException; 057import com.sun.identity.federation.common.FSUtils; 058import com.sun.identity.federation.common.IFSConstants; 059 060/** 061 * This class contains methods for creating a Liberty <code>Response</code>. 062 * 063 * @supported.all.api 064 */ 065public class FSResponse extends Response { 066 protected String id = null; 067 068 /** 069 * Returns the value of <code>id</code> attribute. 070 * 071 * @return the value of <code>id</code> attribute. 072 * @see #setID(String) 073 */ 074 public String getID() { 075 return id; 076 } 077 078 /** 079 * Sets the value of <code>id</code> attribute. 080 * 081 * @param id the value of <code>id</code> attribute. 082 * @see #getID() 083 */ 084 public void setID(String id) { 085 this.id = id; 086 } 087 088 /** 089 * Returns the signed <code>XML</code> string. 090 * 091 * @return the signed <code>XML</code> string. 092 */ 093 public String getSignatureString(){ 094 return signatureString; 095 } 096 097 /** 098 * Returns the <code>MinorVersion</code>. 099 * 100 * @return the <code>MinorVersion</code>. 101 * @see #setMinorVersion(int) 102 */ 103 104 public int getMinorVersion() { 105 return minorVersion; 106 } 107 108 /** 109 * Sets the <code>MinorVersion</code>. 110 * 111 * @param version the <code>MinorVersion</code>. 112 * @see #getMinorVersion() 113 */ 114 public void setMinorVersion(int version) { 115 minorVersion = version; 116 } 117 118 /** 119 * Constructor creates <code>FSResponse</code> object. 120 * 121 * @param responseID value of <code>ResponseId</code> attribute. 122 * @param inResponseTo value of <code>inResponseTo</code> attribute. 123 * @param status the <code>Status</code> object. 124 * @param contents list containing response elements. 125 * @throws SAMLException it there is an error creating this object. 126 * @throws FSMsgException it there is an error creating this object. 127 */ 128 public FSResponse(String responseID, 129 String inResponseTo, 130 Status status, 131 List contents) throws SAMLException, FSMsgException { 132 super( responseID, inResponseTo, status, contents); 133 } 134 135 public static FSResponse parseResponseXML( 136 String xml 137 ) throws SAMLException, FSMsgException { 138 // parse the xml string 139 FSUtils.debug.message("FSResponse.parseResponseXML: Called"); 140 Element root; 141 Document doc = XMLUtils.toDOMDocument(xml, FSUtils.debug); 142 if (doc == null) { 143 FSUtils.debug.error("FSResponse.parseXML:Error " 144 + "while parsing input xml string"); 145 throw new FSMsgException("parseError",null); 146 } 147 root = doc.getDocumentElement(); 148 return new FSResponse(root); 149 } 150 151 /** 152 * Constructor creates <code>FSResponse</code> object form 153 * a Document Element. 154 * 155 * @param root the Document Element object. 156 * @throws SAMLException if there is an error creating this object. 157 * @throws FSMsgException if there is an error creating this object. 158 */ 159 public FSResponse(Element root) throws SAMLException, FSMsgException { 160 FSUtils.debug.message("FSResponse(Element): Called"); 161 if (root == null) { 162 FSUtils.debug.message("FSResponse(Element): " 163 + "Input paramenter (root) is null"); 164 throw new FSMsgException("nullInput",null); 165 } 166 String tag = null; 167 if (((tag = root.getLocalName()) == null) || 168 (!tag.equals("Response"))) { 169 FSUtils.debug.message("FSResponse(Element): " 170 + "Root element name is not Response"); 171 throw new FSMsgException("wrongInput",null); 172 } 173 id = root.getAttribute("id"); 174 responseID = root.getAttribute("ResponseID"); 175 if ((responseID == null) || (responseID.length() == 0)) { 176 if (FSUtils.debug.messageEnabled()) { 177 FSUtils.debug.message("FSResponse(Element): " 178 + "Response doesn't have ResponseID attribute"); 179 } 180 String[] args = { IFSConstants.RESPONSE_ID }; 181 throw new FSMsgException("missingAttribute",args); 182 } 183 184 inResponseTo = root.getAttribute("InResponseTo"); 185 if (inResponseTo == null) { 186 if (FSUtils.debug.messageEnabled()) { 187 FSUtils.debug.message("FSResponse(Element): " 188 + "Response doesn't have InResponseTo attribute"); 189 } 190 String[] args = { IFSConstants.IN_RESPONSE_TO }; 191 throw new FSMsgException("missingAttribute",args); 192 } 193 194 // Attribute IssueInstant 195 String instantString = root.getAttribute("IssueInstant"); 196 if ((instantString == null) || (instantString.length() == 0)) { 197 FSUtils.debug.message("FSResponse(Element): missing IssueInstant"); 198 String[] args = { IFSConstants.ISSUE_INSTANT }; 199 throw new FSMsgException("missingAttribute",args); 200 } else { 201 try { 202 issueInstant = DateUtils.stringToDate(instantString); 203 } catch (ParseException e) { 204 if (FSUtils.debug.messageEnabled()) { 205 FSUtils.debug.message("FSResponse(Element): could not " 206 + "parse IssueInstant:", e); 207 } 208 throw new FSMsgException("wrongInput", null); 209 } 210 } 211 parseMajorVersion(root.getAttribute("MajorVersion")); 212 parseMinorVersion(root.getAttribute("MinorVersion")); 213 setRecipient(root.getAttribute("Recipient")); 214 NodeList nl = root.getChildNodes(); 215 Node child; 216 String childName; 217 int length = nl.getLength(); 218 for (int i = 0; i < length; i++) { 219 child = nl.item(i); 220 if ((childName = child.getLocalName()) != null) { 221 if (childName.equals("Status")) { 222 if (status != null) { 223 if (FSUtils.debug.messageEnabled()) { 224 FSUtils.debug.message( 225 "FSResponse(Element): included more" 226 + " than one <Status>"); 227 } 228 throw new FSMsgException("moreElement",null); 229 } 230 status = new Status((Element) child); 231 } else if (childName.equals("Assertion")) { 232 if (assertions == Collections.EMPTY_LIST) { 233 assertions = new ArrayList(); 234 } 235 assertions.add(new FSAssertion((Element) child)); 236 }else { 237 if (FSUtils.debug.messageEnabled()) { 238 FSUtils.debug.message( 239 "FSResponse(Element): included wrong " 240 + "element: " + childName); 241 } 242 throw new FSMsgException("wrongInput",null); 243 } 244 } // end if childName != null 245 } // end for loop 246 247 if (status == null) { 248 FSUtils.debug.message( 249 "FSResponse(Element): missing element <Status>."); 250 throw new FSMsgException("missingElement",null); 251 } 252 253 //check for signature 254 List signs = XMLUtils.getElementsByTagNameNS1(root, 255 SAMLConstants.XMLSIG_NAMESPACE_URI, 256 SAMLConstants.XMLSIG_ELEMENT_NAME); 257 int signsSize = signs.size(); 258 if (signsSize == 1) { 259 Element elem = (Element)signs.get(0); 260 setSignature(elem); 261 xmlString = XMLUtils.print(root); 262 signed = true; 263 } else if (signsSize != 0) { 264 if (FSUtils.debug.messageEnabled()) { 265 FSUtils.debug.message("FSResponse(Element): included more than" 266 + " one Signature element."); 267 } 268 throw new FSMsgException("moreElement",null); 269 } 270 //end check for signature 271 } 272 273 /** 274 * Sets the <code>MajorVersion</code> by parsing the version string. 275 * 276 * @param majorVer a String representing the <code>MajorVersion</code> to 277 * be set. 278 * @throws SAMLException on error. 279 * @throws FSMsgException if there is an error parsing the version string. 280 */ 281 private void parseMajorVersion(String majorVer) 282 throws SAMLException, FSMsgException { 283 try { 284 majorVersion = Integer.parseInt(majorVer); 285 } catch (NumberFormatException e) { 286 if (FSUtils.debug.messageEnabled()) { 287 FSUtils.debug.message("FSResponse(Element): invalid " 288 + "MajorVersion", e); 289 } 290 throw new FSMsgException("wrongInput",null); 291 } 292 293 if (majorVersion != SAMLConstants.PROTOCOL_MAJOR_VERSION) { 294 if (majorVersion > SAMLConstants.PROTOCOL_MAJOR_VERSION) { 295 if (FSUtils.debug.messageEnabled()) { 296 FSUtils.debug.message("FSResponse(Element):MajorVersion of" 297 + " the Response is too high."); 298 } 299 throw new SAMLVersionMismatchException(FSUtils.BUNDLE_NAME, 300 "responseVersionTooHigh",null); 301 } else { 302 if (FSUtils.debug.messageEnabled()) { 303 FSUtils.debug.message("FSResponse(Element):MajorVersion of" 304 + " the Response is too low."); 305 } 306 throw new SAMLVersionMismatchException(FSUtils.BUNDLE_NAME, 307 "responseVersionTooLow",null); 308 } 309 } 310 } 311 312 /** 313 * Sets the <code>MinorVersion</code> by parsing the version string. 314 * 315 * @param minorVer a String representing the <code>MinorVersion</code> to 316 * be set. 317 * @throws SAMLException when the version mismatchs. 318 * @throws FSMsgException if there is an error 319 * parsing the version string. 320 */ 321 private void parseMinorVersion(String minorVer) 322 throws SAMLException, FSMsgException { 323 try { 324 minorVersion = Integer.parseInt(minorVer); 325 } catch (NumberFormatException e) { 326 if (FSUtils.debug.messageEnabled()) { 327 FSUtils.debug.message("FSResponse(Element): invalid " 328 + "MinorVersion", e); 329 } 330 throw new FSMsgException("wrongInput",null); 331 } 332 333 if (minorVersion > IFSConstants.FF_12_SAML_PROTOCOL_MINOR_VERSION) { 334 FSUtils.debug.error("FSResponse(Element):MinorVersion of" 335 + " the Response is too high."); 336 throw new SAMLVersionMismatchException(FSUtils.BUNDLE_NAME, 337 "responseVersionTooHigh",null); 338 } else if (minorVersion < 339 IFSConstants.FF_11_SAML_PROTOCOL_MINOR_VERSION) { 340 FSUtils.debug.error("FSResponse(Element):MinorVersion of" 341 + " the Response is too low."); 342 throw new SAMLVersionMismatchException(FSUtils.BUNDLE_NAME, 343 "responseVersionTooLow",null); 344 } 345 } 346 347 /** 348 * Returns a String representation of the Logout Response. 349 * 350 * @return a string containing the valid XML for this element 351 * @throws FSMsgException if there is an error converting 352 * this object to a string. 353 */ 354 public String toXMLString() throws FSMsgException { 355 return this.toXMLString(true, true); 356 } 357 358 /** 359 * Returns a String representation of the Logout Response. 360 * 361 * @param includeNS : Determines whether or not the namespace qualifier 362 * is prepended to the Element when converted 363 * @param declareNS : Determines whether or not the namespace is declared 364 * within the Element. 365 * @return a string containing the valid XML for this element 366 * @throws FSMsgException if there is an error converting 367 * this object ot a string. 368 */ 369 370 public String toXMLString(boolean includeNS, boolean declareNS) 371 throws FSMsgException { 372 return toXMLString(includeNS, declareNS, false); 373 } 374 375 public String toXMLString(boolean includeNS,boolean declareNS, 376 boolean includeHeader) throws FSMsgException { 377 FSUtils.debug.message("FSResponse.toXMLString(3): Called"); 378 StringBuffer xml = new StringBuffer(500); 379 if (includeHeader) { 380 xml.append("<?xml version=\"1.0\" encoding=\""). 381 append(SAMLConstants.DEFAULT_ENCODING).append("\" ?>"); 382 } 383 String prefixSAML=null; 384 String prefixLIB=null; 385 String prefixSAML_PROTOCOL = ""; 386 String uriSAML_PROTOCOL = ""; 387 String uriSAML = ""; 388 String uriLIB = ""; 389 String uriDS=""; 390 String uriXSI=""; 391 392 if (includeNS) { 393 prefixLIB = IFSConstants.LIB_PREFIX; 394 prefixSAML = IFSConstants.ASSERTION_PREFIX; 395 prefixSAML_PROTOCOL = IFSConstants.PROTOCOL_PREFIX; 396 } 397 if (declareNS) { 398 if(minorVersion == IFSConstants.FF_12_SAML_PROTOCOL_MINOR_VERSION){ 399 uriLIB = IFSConstants.LIB_12_NAMESPACE_STRING; 400 } else { 401 uriLIB = IFSConstants.LIB_NAMESPACE_STRING; 402 } 403 uriSAML = IFSConstants.assertionDeclareStr; 404 uriSAML_PROTOCOL = IFSConstants.PROTOCOL_NAMESPACE_STRING; 405 uriDS = IFSConstants.DSSAMLNameSpace; 406 uriXSI = IFSConstants.XSI_NAMESPACE_STRING; 407 } 408 409 String instantString = DateUtils.toUTCDateFormat(issueInstant); 410 411 if((responseID != null) && (inResponseTo != null)){ 412 xml.append("<").append(prefixSAML_PROTOCOL).append("Response"). 413 append(uriLIB). 414 append(uriSAML).append(uriSAML_PROTOCOL).append(" "). 415 append(uriDS). 416 append(" ").append(uriXSI).append(" ResponseID=\""). 417 append(responseID).append("\" "); 418 if ((inResponseTo != null) && (inResponseTo.length() != 0)) { 419 xml.append(" InResponseTo=\"").append(inResponseTo). 420 append("\" "); 421 } 422 if (minorVersion == IFSConstants.FF_11_PROTOCOL_MINOR_VERSION && 423 id != null && !(id.length() == 0)){ 424 xml.append(" id=\"").append(id).append("\""); 425 } 426 xml.append(" MajorVersion=\""). 427 append(majorVersion).append("\" "). 428 append(" MinorVersion=\"").append(minorVersion). 429 append("\" "). 430 append(" IssueInstant=\"").append(instantString). 431 append("\""); 432 if ((recipient != null) && (recipient.length() != 0)) { 433 xml.append(" Recipient=\"").append(recipient).append("\" "); 434 } 435 xml.append(">"); 436 } 437 438 if (signed) { 439 if (signatureString != null) { 440 xml.append(signatureString); 441 } else if (signature != null) { 442 signatureString = XMLUtils.print(signature); 443 xml.append(signatureString); 444 } 445 } 446 447 if(status != null) 448 xml.append(status.toString(includeNS, false)); 449 450 if ((assertions != null) && (assertions != Collections.EMPTY_LIST)) { 451 Iterator j = assertions.iterator(); 452 while (j.hasNext()) { 453 xml.append(((FSAssertion) j.next()). 454 toXMLString(true,declareNS)); 455 } 456 } 457 458 xml.append("</").append(prefixSAML_PROTOCOL).append("Response>"); 459 return xml.toString(); 460 } 461 462 /** 463 * Returns <code>FSResponse</code> object. The object 464 * is created by parsing an Base64 encoded response string. 465 * 466 * @param encodedRes the encoded response string 467 * @throws FSMsgException if there is an error creating 468 * <code>FSResponse</code> object. 469 * @throws FSMsgException if there is an error creating 470 * <code>FSResponse</code> object. 471 */ 472 public static FSResponse parseBASE64EncodedString( 473 String encodedRes) throws FSMsgException, SAMLException { 474 FSUtils.debug.message("FSResponse.parseBASE64EncodedString:Called new"); 475 if (encodedRes != null) { 476 String decodedAuthnRes = new String(Base64.decode(encodedRes)); 477 if (FSUtils.debug.messageEnabled()) { 478 FSUtils.debug.message("FSResponse.parseBASE64EncodedString:" 479 + "Decoded AuthnResponse message: " 480 + decodedAuthnRes); 481 } 482 return parseResponseXML(decodedAuthnRes); 483 } else{ 484 if (FSUtils.debug.messageEnabled()) { 485 FSUtils.debug.message("FSResponse.parseBASE64EncodedString:" 486 + "null String passed in as argument."); 487 } 488 throw new FSMsgException("nullInput",null); 489 } 490 } 491 492 /** 493 * Returns a Base64 Encoded String. 494 * 495 * @return a Base64 Encoded String. 496 * @throws FSMsgException if there is an error encoding the string. 497 */ 498 public String toBASE64EncodedString() throws FSMsgException { 499 FSUtils.debug.message("FSResponse.toBASE64EncodedString: Called"); 500 if ((responseID == null) || (responseID.length() == 0)){ 501 responseID = FSUtils.generateID(); 502 if (responseID == null) { 503 FSUtils.debug.error("FSResponse.toBASE64EncodedString: " 504 + "couldn't generate ResponseID."); 505 throw new FSMsgException("errorGenerateID",null); 506 } 507 } 508 return Base64.encode(this.toXMLString().getBytes()); 509 } 510 511 /** 512 * Signs the Response. 513 * 514 * @param certAlias the Certificate Alias. 515 * @throws XMLSignatureException if <code>FSAuthnRequest</code> 516 * cannot be signed. 517 */ 518 public void signXML(String certAlias) throws SAMLException { 519 FSUtils.debug.message("FSResponse.signXML: Called"); 520 if (signed) { 521 if (FSUtils.debug.messageEnabled()) { 522 FSUtils.debug.message("FSResponse.signXML: the assertion is " 523 + "already signed."); 524 } 525 throw new SAMLResponderException(FSUtils.BUNDLE_NAME, 526 "alreadySigned",null); 527 } 528 if (certAlias == null || certAlias.length() == 0) { 529 throw new SAMLResponderException(FSUtils.BUNDLE_NAME, 530 "cannotFindCertAlias",null); 531 } 532 try { 533 XMLSignatureManager manager = XMLSignatureManager.getInstance(); 534 if (minorVersion == IFSConstants.FF_11_PROTOCOL_MINOR_VERSION) { 535 signatureString = manager.signXML( 536 this.toXMLString(true, true), 537 certAlias, IFSConstants.DEF_SIG_ALGO, 538 IFSConstants.ID, 539 this.id, false); 540 } else if (minorVersion == 541 IFSConstants.FF_12_PROTOCOL_MINOR_VERSION) { 542 signatureString = manager.signXML( 543 this.toXMLString(true, true), 544 certAlias, IFSConstants.DEF_SIG_ALGO, 545 IFSConstants.RESPONSE_ID, 546 this.getResponseID(), false); 547 } else { 548 if (FSUtils.debug.messageEnabled()) { 549 FSUtils.debug.message("invalid minor version."); 550 } 551 } 552 553 signature = 554 XMLUtils.toDOMDocument(signatureString, FSUtils.debug) 555 .getDocumentElement(); 556 557 signed = true; 558 xmlString = this.toXMLString(true, true); 559 } catch(Exception e){ 560 throw new SAMLResponderException(FSUtils.BUNDLE_NAME, 561 "signFailed",null); 562 } 563 } 564 565 /** 566 * Unsupported operation. 567 */ 568 public void signXML() throws SAMLException { 569 throw new SAMLException(FSUtils.BUNDLE_NAME, 570 "unsupportedOperation",null); 571 } 572}