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: Response.java,v 1.3 2009/02/13 04:05:10 bina Exp $ 026 * 027 */ 028 029/** 030 * Portions Copyrighted 2014 ForgeRock AS 031 */ 032package com.sun.identity.saml.protocol; 033 034import com.sun.identity.common.SystemConfigurationUtil; 035import com.sun.identity.shared.xml.XMLUtils; 036 037import com.sun.identity.shared.DateUtils; 038 039import com.sun.identity.saml.assertion.Assertion; 040 041import com.sun.identity.saml.common.SAMLConstants; 042import com.sun.identity.saml.common.SAMLException; 043import com.sun.identity.saml.common.SAMLRequesterException; 044import com.sun.identity.saml.common.SAMLRequestVersionTooHighException; 045import com.sun.identity.saml.common.SAMLRequestVersionTooLowException; 046import com.sun.identity.saml.common.SAMLResponderException; 047import com.sun.identity.saml.common.SAMLUtils; 048import com.sun.identity.saml.common.SAMLVersionMismatchException; 049 050import com.sun.identity.saml.xmlsig.XMLSignatureManager; 051 052import java.io.ByteArrayOutputStream; 053import java.io.InputStream; 054 055import java.text.ParseException; 056 057import java.util.ArrayList; 058import java.util.Collections; 059import java.util.Date; 060import java.util.Iterator; 061import java.util.List; 062 063import org.w3c.dom.Document; 064import org.w3c.dom.Element; 065import org.w3c.dom.Node; 066import org.w3c.dom.NodeList; 067 068/** 069 * This <code>Response</code> class represents a Response XML document. 070 * The schema of Response is defined as the following: 071 * 072 * @supported.all.api 073 */ 074public class Response extends AbstractResponse { 075 076 protected Status status = null; 077 protected List assertions = Collections.EMPTY_LIST; 078 protected String xmlString = null; 079 protected String signatureString = null; 080 protected String issuer = null; 081 082 // Response ID attribute name 083 private static final String RESPONSE_ID_ATTRIBUTE = "ResponseID"; 084 085 /** default constructor */ 086 protected Response() {} 087 088 /** 089 * Return whether the signature on the object is valid or not. 090 * @return true if the signature on the object is valid; false otherwise. 091 */ 092 public boolean isSignatureValid() { 093 if (signed & ! validationDone) { 094 valid = SAMLUtils.checkSignatureValid( 095 xmlString, RESPONSE_ID_ATTRIBUTE, issuer); 096 097 validationDone = true; 098 } 099 return valid; 100 } 101 102 /** 103 * Method that signs the Response. 104 * 105 * @exception SAMLException if could not sign the Response. 106 */ 107 public void signXML() throws SAMLException { 108 if (signed) { 109 if (SAMLUtils.debug.messageEnabled()) { 110 SAMLUtils.debug.message("Response.signXML: the response is " 111 + "already signed."); 112 } 113 throw new SAMLException( 114 SAMLUtils.bundle.getString("alreadySigned")); 115 } 116 String certAlias = 117 SystemConfigurationUtil.getProperty( 118 "com.sun.identity.saml.xmlsig.certalias"); 119 if (certAlias == null) { 120 if (SAMLUtils.debug.messageEnabled()) { 121 SAMLUtils.debug.message("Response.signXML: couldn't obtain " 122 + "this site's cert alias."); 123 } 124 throw new SAMLResponderException( 125 SAMLUtils.bundle.getString("cannotFindCertAlias")); 126 } 127 XMLSignatureManager manager = XMLSignatureManager.getInstance(); 128 if ((majorVersion == 1) && (minorVersion == 0)) { 129 SAMLUtils.debug.message("Request.signXML: sign with version 1.0"); 130 signatureString = manager.signXML(this.toString(true, true), 131 certAlias); 132 // this block is used for later return of signature element by 133 // getSignature() method 134 signature = 135 XMLUtils.toDOMDocument(signatureString, SAMLUtils.debug) 136 .getDocumentElement(); 137 } else { 138 Document doc = XMLUtils.toDOMDocument(this.toString(true, true), 139 SAMLUtils.debug); 140 // sign with SAML 1.1 spec & include cert in KeyInfo 141 signature = manager.signXML(doc, certAlias, null, 142 RESPONSE_ID_ATTRIBUTE, getResponseID(), true, null); 143 signatureString = XMLUtils.print(signature); 144 } 145 signed = true; 146 xmlString = this.toString(true, true); 147 } 148 149 private void buildResponse(String responseID, 150 String inResponseTo, 151 Status status, 152 String recipient, 153 List contents) throws SAMLException 154 { 155 if ((responseID == null) || (responseID.length() == 0)) { 156 // generate one 157 this.responseID = SAMLUtils.generateID(); 158 if (this.responseID == null) { 159 throw new SAMLRequesterException( 160 SAMLUtils.bundle.getString("errorGenerateID")); 161 } 162 } else { 163 this.responseID = responseID; 164 } 165 166 this.inResponseTo = inResponseTo; 167 168 this.recipient = recipient; 169 170 issueInstant = new Date(); 171 172 if (status == null) { 173 SAMLUtils.debug.message("Response: missing <Status>."); 174 throw new SAMLRequesterException( 175 SAMLUtils.bundle.getString("missingElement")); 176 } 177 this.status = status; 178 179 if ((contents != null) && 180 (contents != Collections.EMPTY_LIST)) { 181 int length = contents.size(); 182 for (int i = 0; i < length; i++) { 183 Object temp = contents.get(i); 184 if (!(temp instanceof Assertion)) { 185 if (SAMLUtils.debug.messageEnabled()) { 186 SAMLUtils.debug.message("Response: Wrong input " 187 + "for Assertion."); 188 } 189 throw new SAMLRequesterException( 190 SAMLUtils.bundle.getString("wrongInput")); 191 } 192 } 193 assertions = contents; 194 } 195 } 196 197 /** 198 * This constructor shall only be used at the server side to construct 199 * a Response object. 200 * NOTE: The content here is just the body for the Response. The 201 * constructor will add the unique <code>ResponseID</code>, 202 * <code>MajorVersion</code>, etc. to form a complete Response object. 203 * 204 * @param responseID If it's null, the constructor will create one. 205 * @param inResponseTo the <code>RequestID</code> that this response is 206 * corresponding. It could be null or empty string "". 207 * @param status The status of the response. 208 * @param contents A List of Assertions that are the content of the 209 * Response. It could be null when there is no Assertion. 210 * @throws SAMLException if error occurs. 211 */ 212 public Response(String responseID, 213 String inResponseTo, 214 Status status, 215 List contents) throws SAMLException 216 { 217 buildResponse(responseID, inResponseTo, status, null, contents); 218 } 219 220 /** 221 * This constructor shall only be used at the server side to construct 222 * a Response object. 223 * NOTE: The content here is just the body for the Response. The 224 * constructor will add the unique <code>ResponseID</code>, 225 * <code>MajorVersion</code>, etc. to form a complete Response object. 226 * 227 * @param responseID If it's null, the constructor will create one. 228 * @param inResponseTo the <code>RequestID</code> that this response is 229 * corresponding. It could be null or empty string "". 230 * @param status The status of the response. 231 * @param recipient The intended recipient of the response. It could be 232 * null or empty string since it's optional. 233 * @param contents A List of Assertions that are the content of the 234 * Response. It could be null when there is no Assertion. 235 * @throws SAMLException if error occurs. 236 */ 237 public Response(String responseID, 238 String inResponseTo, 239 Status status, 240 String recipient, 241 List contents) throws SAMLException 242 { 243 buildResponse(responseID, inResponseTo, status, recipient, contents); 244 } 245 246 /** 247 * This constructor shall only be used at the server side to construct 248 * a Response object. 249 * NOTE: The content here is just the body for the Response. The 250 * constructor will add the unique <code>ResponseID</code>, 251 * <code>MajorVersion</code>, etc. to form a complete Response object. 252 * 253 * @param responseID If it's null, the constructor will create one. 254 * @param status The status of the response. 255 * @param recipient The intended recipient of the response. It could be 256 * null or empty string since it's optional. 257 * @param contents A List of Assertions that are the content of the 258 * Response. It could be null when there is no Assertion. 259 * @throws SAMLException if error occurs. 260 */ 261 public Response(String responseID, 262 Status status, 263 String recipient, 264 List contents) throws SAMLException 265 { 266 buildResponse(responseID, null, status, recipient, contents); 267 } 268 269 /** 270 * This constructor shall only be used at the server side to construct 271 * a Response object. 272 * NOTE: The content here is just the body for the Response. The 273 * constructor will add the unique <code>ResponseID</code>, 274 * <code>MajorVersion</code>, etc. to form a complete Response object. 275 * 276 * @param responseID If it's null, the constructor will create one. 277 * @param status The status of the response. 278 * @param contents A List of Assertions that are the content of the 279 * Response. It could be null when there is no Assertion. 280 * @throws SAMLException if error occurs. 281 */ 282 public Response(String responseID, 283 Status status, 284 List contents) throws SAMLException 285 { 286 buildResponse(responseID, null, status, null, contents); 287 } 288 289 /** 290 * Returns Response object based on the XML document received from server. 291 * This method is used primarily at the client side. The schema of the XML 292 * document is describe above. 293 * 294 * @param xml The Response XML document String. 295 * NOTE: this is a complete SAML response XML string with 296 * <code>ResponseID</code>, <code>MajorVersion</code>, etc. 297 * @return Response object based on the XML document received from server. 298 * @exception SAMLException if XML parsing failed 299 */ 300 public static Response parseXML(String xml) throws SAMLException { 301 // parse the xml string 302 Document doc = XMLUtils.toDOMDocument(xml, SAMLUtils.debug); 303 Element root = doc.getDocumentElement(); 304 305 return new Response(root); 306 } 307 308 /** 309 * Returns Response object based on the XML document received from server. 310 * This method is used primarily at the client side. The schema of the XML 311 * document is describe above. 312 * 313 * @param is The Response XML <code>InputStream</code>. 314 * NOTE: The <code>InputStream</code> contains a complete 315 * SAML response with 316 * <code>ResponseID</code>, <code>MajorVersion</code>, etc. 317 * @return Response object based on the XML document received from server. 318 * @exception SAMLException if XML parsing failed 319 */ 320 public static Response parseXML(InputStream is) throws SAMLException { 321 Document doc = XMLUtils.toDOMDocument(is, SAMLUtils.debug); 322 Element root = doc.getDocumentElement(); 323 324 return new Response(root); 325 } 326 327 /** 328 * Constructor. 329 * 330 * @param root <code>Response</code> element 331 * @throws SAMLException if error occurs. 332 */ 333 public Response(Element root) throws SAMLException { 334 // Make sure this is a Response 335 if (root == null) { 336 SAMLUtils.debug.message("Response(Element): null input."); 337 throw new SAMLRequesterException( 338 SAMLUtils.bundle.getString("nullInput")); 339 } 340 String tag = null; 341 if (((tag = root.getLocalName()) == null) || 342 (!tag.equals("Response"))) { 343 SAMLUtils.debug.message("Response(Element): wrong input."); 344 throw new SAMLRequesterException( 345 SAMLUtils.bundle.getString("wrongInput")); 346 } 347 348 List signs = XMLUtils.getElementsByTagNameNS1(root, 349 SAMLConstants.XMLSIG_NAMESPACE_URI, 350 SAMLConstants.XMLSIG_ELEMENT_NAME); 351 int signsSize = signs.size(); 352 if (signsSize == 1) { 353 xmlString = XMLUtils.print(root); 354 signed = true; 355 } else if (signsSize != 0) { 356 if (SAMLUtils.debug.messageEnabled()) { 357 SAMLUtils.debug.message("Response(Element): included more than" 358 + " one Signature element."); 359 } 360 throw new SAMLRequesterException( 361 SAMLUtils.bundle.getString("moreElement")); 362 } 363 364 // Attribute ResponseID 365 responseID = root.getAttribute("ResponseID"); 366 if ((responseID == null) || (responseID.length() == 0)) { 367 if (SAMLUtils.debug.messageEnabled()) { 368 SAMLUtils.debug.message("Response.parseXML: " 369 + "Reponse doesn't have ResponseID."); 370 } 371 throw new SAMLRequesterException( 372 SAMLUtils.bundle.getString("missingAttribute")); 373 } 374 375 // Attribute InResponseTo 376 if (root.hasAttribute("InResponseTo")) { 377 inResponseTo = root.getAttribute("InResponseTo"); 378 } 379 380 // Attribute MajorVersion 381 parseMajorVersion(root.getAttribute("MajorVersion")); 382 383 parseMinorVersion(root.getAttribute("MinorVersion")); 384 385 if (root.hasAttribute("Recipient")) { 386 recipient = root.getAttribute("Recipient"); 387 } 388 389 // Attribute IssueInstant 390 String instantString = root.getAttribute("IssueInstant"); 391 if ((instantString == null) || (instantString.length() == 0)) { 392 SAMLUtils.debug.message("Response(Element): missing IssueInstant"); 393 throw new SAMLRequesterException( 394 SAMLUtils.bundle.getString("missingAttribute")); 395 } else { 396 try { 397 issueInstant = DateUtils.stringToDate(instantString); 398 } catch (ParseException e) { 399 SAMLUtils.debug.message( 400 "Resposne(Element): could not parse IssueInstant", e); 401 throw new SAMLRequesterException(SAMLUtils.bundle.getString( 402 "wrongInput")); 403 } 404 } 405 406 NodeList nl = root.getChildNodes(); 407 Node child; 408 String childName; 409 int length = nl.getLength(); 410 for (int i = 0; i < length; i++) { 411 child = nl.item(i); 412 if ((childName = child.getLocalName()) != null) { 413 if (childName.equals("Signature")) { 414 signature = (Element) child; 415 } else if (childName.equals("Status")) { 416 if (status != null) { 417 if (SAMLUtils.debug.messageEnabled()) { 418 SAMLUtils.debug.message("Response: included more" 419 + " than one <Status>"); 420 } 421 throw new SAMLRequesterException( 422 SAMLUtils.bundle.getString("moreElement")); 423 } 424 status = new Status((Element) child); 425 } else if (childName.equals("Assertion")) { 426 if (assertions == Collections.EMPTY_LIST) { 427 assertions = new ArrayList(); 428 } 429 Element canoEle = SAMLUtils.getCanonicalElement(child); 430 if (canoEle == null) { 431 throw new SAMLRequesterException( 432 SAMLUtils.bundle.getString("errorCanonical")); 433 } 434 435 Assertion oneAssertion= new Assertion(canoEle); 436 issuer = oneAssertion.getIssuer(); 437 assertions.add(oneAssertion); 438 } else { 439 if (SAMLUtils.debug.messageEnabled()) { 440 SAMLUtils.debug.message("Response: included wrong " 441 + "element:" + childName); 442 } 443 throw new SAMLRequesterException( 444 SAMLUtils.bundle.getString("wrongInput")); 445 } 446 } // end if childName != null 447 } // end for loop 448 449 if (status == null) { 450 SAMLUtils.debug.message("Response: missing element <Status>."); 451 throw new SAMLRequesterException( 452 SAMLUtils.bundle.getString("oneElement")); 453 } 454 } 455 456 /** 457 * Parse the input and set the majorVersion accordingly. 458 * @param majorVer a String representing the MajorVersion to be set. 459 * @exception SAMLException when the version mismatchs. 460 */ 461 private void parseMajorVersion(String majorVer) throws SAMLException { 462 try { 463 majorVersion = Integer.parseInt(majorVer); 464 } catch (NumberFormatException e) { 465 if (SAMLUtils.debug.messageEnabled()) { 466 SAMLUtils.debug.message("Response(Element): invalid " 467 + "MajorVersion", e); 468 } 469 throw new SAMLRequesterException( 470 SAMLUtils.bundle.getString("wrongInput")); 471 } 472 473 if (majorVersion != SAMLConstants.PROTOCOL_MAJOR_VERSION) { 474 if (majorVersion > SAMLConstants.PROTOCOL_MAJOR_VERSION) { 475 if (SAMLUtils.debug.messageEnabled()) { 476 SAMLUtils.debug.message("Response(Element):MajorVersion of" 477 + " the Response is too high."); 478 } 479 throw new SAMLVersionMismatchException( 480 SAMLUtils.bundle.getString("responseVersionTooHigh")); 481 } else { 482 if (SAMLUtils.debug.messageEnabled()) { 483 SAMLUtils.debug.message("Response(Element):MajorVersion of" 484 + " the Response is too low."); 485 } 486 throw new SAMLVersionMismatchException( 487 SAMLUtils.bundle.getString("responseVersionTooLow")); 488 } 489 } 490 } 491 492 /** 493 * Parse the input and set the minorVersion accordingly. 494 * @param minorVer a String representing the MinorVersion to be set. 495 * @exception SAMLException when the version mismatchs. 496 */ 497 private void parseMinorVersion(String minorVer) throws SAMLException { 498 try { 499 minorVersion = Integer.parseInt(minorVer); 500 } catch (NumberFormatException e) { 501 if (SAMLUtils.debug.messageEnabled()) { 502 SAMLUtils.debug.message("Response(Element): invalid " 503 + "MinorVersion", e); 504 } 505 throw new SAMLRequesterException( 506 SAMLUtils.bundle.getString("wrongInput")); 507 } 508 509 if (minorVersion > SAMLConstants.PROTOCOL_MINOR_VERSION_ONE) { 510 if (SAMLUtils.debug.messageEnabled()) { 511 SAMLUtils.debug.message("Response(Element): MinorVersion" 512 + " of the Response is too high."); 513 } 514 throw new SAMLRequestVersionTooHighException( 515 SAMLUtils.bundle.getString("responseVersionTooHigh")); 516 } else if (minorVersion < SAMLConstants.PROTOCOL_MINOR_VERSION_ZERO) { 517 if (SAMLUtils.debug.messageEnabled()) { 518 SAMLUtils.debug.message("Response(Element): MinorVersion" 519 + " of the Response is too low."); 520 } 521 throw new SAMLRequestVersionTooLowException( 522 SAMLUtils.bundle.getString("responseVersionTooLow")); 523 } 524 } 525 526 /** 527 * This method returns the set of Assertions that is the content of 528 * the response. 529 * @return The set of Assertions that is the content of the response. 530 * It could be Collections.EMPTY_LIST when there is no Assertion 531 * in the response. 532 */ 533 public List getAssertion() { 534 return assertions; 535 } 536 537 /** 538 * Add an assertion to the Response. 539 * @param assertion The assertion to be added. 540 * @return A boolean value: true if the operation is successful; 541 * false otherwise. 542 */ 543 public boolean addAssertion(Assertion assertion) { 544 if (signed) { 545 return false; 546 } 547 if (assertion == null) { 548 return false; 549 } 550 if ((assertions == null) || (assertions == Collections.EMPTY_LIST)) { 551 assertions = new ArrayList(); 552 } 553 assertions.add(assertion); 554 return true; 555 } 556 557 /** 558 * Gets the Status of the Response. 559 * @return The Status of the response. 560 */ 561 public Status getStatus() { 562 return status; 563 } 564 565 /** 566 * Set the Status of the Response. 567 * 568 * @param status The Status of the Response to be set. 569 * @return true if the operation is successful. 570 */ 571 public boolean setStatus(Status status) { 572 if (signed) { 573 return false; 574 } 575 if (status == null) { 576 return false; 577 } 578 this.status = status; 579 return true; 580 } 581 582 /** 583 * Set the signature for the Response. 584 * @param elem ds:Signature element 585 * @return A boolean value: true if the operation succeeds; false otherwise. 586 */ 587 public boolean setSignature(Element elem) { 588 signatureString = XMLUtils.print(elem); 589 return super.setSignature(elem); 590 } 591 592 /** 593 * This method translates the response to an XML document String based on 594 * the Response schema described above. 595 * @return An XML String representing the response. NOTE: this is a 596 * complete SAML response XML string with <code>ResponseID</code>, 597 * <code>MajorVersion</code>, etc. 598 */ 599 public String toString() { 600 return this.toString(true, true); 601 } 602 603 /** 604 * Creates a String representation of the 605 * <code><samlp:Response></code> element. 606 * 607 * @param includeNS Determines whether or not the namespace qualifier 608 * is prepended to the Element when converted 609 * @param declareNS Determines whether or not the namespace is declared 610 * within the Element. 611 * @return A string containing the valid XML for this element 612 */ 613 public String toString(boolean includeNS, boolean declareNS) { 614 return toString(includeNS, declareNS, false); 615 } 616 617 /** 618 * Creates a String representation of the 619 * <code><samlp:Response></code> element. 620 * 621 * @param includeNS Determines whether or not the namespace qualifier 622 * is prepended to the Element when converted 623 * @param declareNS Determines whether or not the namespace is declared 624 * within the Element. 625 * @param includeHeader Determines whether the output include the XML 626 * declaration header. 627 * @return A string containing the valid XML for this element 628 */ 629 public String toString(boolean includeNS, 630 boolean declareNS, 631 boolean includeHeader) { 632 if (signed && (xmlString != null)) { 633 return xmlString; 634 } 635 636 StringBuffer xml = new StringBuffer(300); 637 if (includeHeader) { 638 xml.append("<?xml version=\"1.0\" encoding=\""). 639 append(SAMLConstants.DEFAULT_ENCODING).append("\" ?>\n"); 640 } 641 String prefix = ""; 642 String uri = ""; 643 if (includeNS) { 644 prefix = SAMLConstants.PROTOCOL_PREFIX; 645 } 646 647 if (declareNS) { 648 uri = SAMLConstants.PROTOCOL_NAMESPACE_STRING; 649 } 650 651 String instantString = DateUtils.toUTCDateFormat(issueInstant); 652 653 xml.append("<").append(prefix).append("Response").append(uri). 654 append(" ResponseID=\"").append(responseID).append("\""); 655 if (inResponseTo != null) { 656 xml.append(" InResponseTo=\"").append(inResponseTo).append("\""); 657 } 658 xml.append(" MajorVersion=\"").append(majorVersion).append("\""). 659 append(" MinorVersion=\"").append(minorVersion).append("\""). 660 append(" IssueInstant=\"").append(instantString).append("\""); 661 if (recipient != null) { 662 xml.append(" Recipient=\"").append(XMLUtils.escapeSpecialCharacters(recipient)).append("\""); 663 } 664 xml.append(">\n"); 665 666 if (signed) { 667 if (signatureString != null) { 668 xml.append(signatureString); 669 } else if (signature != null) { 670 signatureString = XMLUtils.print(signature); 671 xml.append(signatureString); 672 } 673 } 674 675 xml.append(status.toString(includeNS, false)); 676 if ((assertions != null) && (assertions != Collections.EMPTY_LIST)) { 677 Iterator j = assertions.iterator(); 678 while (j.hasNext()) { 679 xml.append(((Assertion) j.next()).toString(true, true)); 680 } 681 } 682 683 xml.append("</").append(prefix).append("Response>\n"); 684 return xml.toString(); 685 } 686}
Copyright © 2010-2017, ForgeRock All Rights Reserved.