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: FSNameIdentifierMappingResponse.java,v 1.2 2008/06/25 05:46:44 qcheng Exp $ 026 * 027 */ 028 029package com.sun.identity.federation.message; 030 031import com.sun.identity.shared.xml.XMLUtils; 032 033import com.sun.identity.shared.Constants; 034import com.sun.identity.shared.DateUtils; 035import com.sun.identity.common.SystemConfigurationUtil; 036 037import com.sun.identity.federation.common.FSUtils; 038import com.sun.identity.federation.common.IFSConstants; 039import com.sun.identity.federation.message.common.FSMsgException; 040 041import com.sun.identity.saml.assertion.NameIdentifier; 042 043import com.sun.identity.saml.common.SAMLConstants; 044import com.sun.identity.saml.common.SAMLException; 045import com.sun.identity.saml.common.SAMLResponderException; 046 047import com.sun.identity.saml.protocol.AbstractResponse; 048import com.sun.identity.saml.protocol.Status; 049 050import com.sun.identity.saml.xmlsig.XMLSignatureManager; 051 052import java.util.Date; 053import java.util.List; 054 055import org.w3c.dom.Document; 056import org.w3c.dom.Element; 057import org.w3c.dom.Node; 058import org.w3c.dom.NodeList; 059 060/** 061 * The class <code>FSNameIdentifierMappingResponse</code> is used to 062 * create , parse the <code>NameIdentifierMappingResponse</code>. 063 * 064 * @supported.all.api 065 */ 066public class FSNameIdentifierMappingResponse extends AbstractResponse { 067 068 private String providerID; 069 private Status status; 070 private NameIdentifier nameIdentifier; 071 private int minorVersion = IFSConstants.FF_12_PROTOCOL_MINOR_VERSION; 072 private String signatureString; 073 074 /** 075 * Constructor to create <code>FSNameIdentifierMappingResponse</code> object. 076 * 077 * @param providerID the value of <code>ProviderID</code> attribute. 078 * @param inResponseTo the value of <code>InResponseTo</code> attribute. 079 * @param status the <code>Status</code> object. 080 * @param nameIdentifier the resulting mapped identifier, 081 * <code>NameIdentifier</code>, for the desired identity federation. 082 * @throws FSMsgException on an error. 083 */ 084 public FSNameIdentifierMappingResponse(String providerID, 085 String inResponseTo, Status status, 086 NameIdentifier nameIdentifier) throws FSMsgException { 087 this.providerID = providerID; 088 this.inResponseTo = inResponseTo; 089 this.status = status; 090 this.nameIdentifier = nameIdentifier; 091 this.responseID = FSUtils.generateID(); 092 setIssueInstant(new Date()); 093 } 094 095 /** 096 * Constructor to create <code>FSNameIdentifierMappingResponse</code> object. 097 * This object is created from the Document Element. 098 * 099 * @param root the <code>NameIdentifierMappingResponse</code> Document 100 * Element 101 * @throws FSMsgException if there is an error creating the object. 102 */ 103 public FSNameIdentifierMappingResponse(Element root) 104 throws FSMsgException { 105 if (root == null) { 106 FSUtils.debug.message( 107 "FSNameIdentifierMappingResponse: null element input"); 108 throw new FSMsgException("nullInputParameter",null); 109 } 110 String tag = null; 111 if (((tag = root.getLocalName()) == null) || 112 (!tag.equals("NameIdentifierMappingResponse"))) { 113 FSUtils.debug.message( 114 "FSNameIdentifierMappingRequest: wrong input"); 115 throw new FSMsgException("wrongInput",null); 116 } 117 118 // get IssueInstant 119 String instantString = root.getAttribute(IFSConstants.ISSUE_INSTANT); 120 if (instantString==null || instantString.length()==0) { 121 FSUtils.debug.error("FSNameIdentifierMappingResponse: " + 122 "missing IssueInstant"); 123 String[] args = { IFSConstants.ISSUE_INSTANT }; 124 throw new FSMsgException("missingAttribute",args); 125 } else { 126 try { 127 issueInstant = DateUtils.stringToDate(instantString); 128 } catch (Exception e) { 129 FSUtils.debug.error("FSNameIdentifierMappingResponse: " + 130 "could not parse IssueInstant.", e); 131 throw new FSMsgException("wrongInput",null); 132 } 133 } 134 135 // get ResponseID 136 responseID = root.getAttribute(IFSConstants.RESPONSE_ID); 137 if ((responseID == null) || (responseID.length() < 1)) { 138 FSUtils.debug.error("FSNameIdentifierMappingResponse: " + 139 "response doesn't have ResponseID"); 140 String[] args = { IFSConstants.RESPONSE_ID }; 141 throw new FSMsgException("missingAttribute",args); 142 } 143 144 // get InResponseTo 145 inResponseTo = root.getAttribute(IFSConstants.IN_RESPONSE_TO); 146 if (inResponseTo == null) { 147 FSUtils.debug.error("FSNameIdentifierMappingResponse: " + 148 "response doesn't have InResponseTo"); 149 String[] args = { IFSConstants.IN_RESPONSE_TO }; 150 throw new FSMsgException("missingAttribute",args); 151 } 152 153 // get and check versions 154 parseMajorVersion(root.getAttribute(IFSConstants.MAJOR_VERSION)); 155 parseMinorVersion(root.getAttribute(IFSConstants.MINOR_VERSION)); 156 157 // get ProviderID, Status & NameIdentifier 158 NodeList nl = root.getChildNodes(); 159 Node child; 160 String childName; 161 int length = nl.getLength(); 162 for (int i = 0; i < length; i++) { 163 child = nl.item(i); 164 if ((childName = child.getLocalName()) != null) { 165 if (childName.equals(IFSConstants.STATUS)) { 166 try { 167 status = new Status((Element) child); 168 } catch (SAMLException se) { 169 FSUtils.debug.error("FSNameIdentifierMappingResponse:" + 170 " unable to initialize Status", se); 171 throw new FSMsgException("statusCreateError",null,se); 172 } 173 } else if (childName.equals(IFSConstants.PROVIDER_ID)) { 174 providerID = XMLUtils.getElementValue((Element) child); 175 } else if (childName.equals(IFSConstants.NAME_IDENTIFIER)) { 176 try { 177 nameIdentifier = 178 new NameIdentifier((Element) child); 179 } catch (SAMLException samle) { 180 FSUtils.debug.error("FSNameIdentifierMappingResponse:" + 181 " unable to initialize NameIdentifier", samle); 182 throw new FSMsgException("nameIdentifierCreateError", 183 null,samle); 184 } 185 } 186 } 187 } 188 189 // get signature 190 List signs = XMLUtils.getElementsByTagNameNS1( 191 root, 192 SAMLConstants.XMLSIG_NAMESPACE_URI, 193 SAMLConstants.XMLSIG_ELEMENT_NAME); 194 int signsSize = signs.size(); 195 if (signsSize == 1) { 196 Element elem = (Element)signs.get(0); 197 setSignature(elem); 198 signed = true; 199 } else if (signsSize != 0) { 200 FSUtils.debug.error("FSNameIdentifierMappingResponse: " + 201 "included more than one Signature element."); 202 throw new FSMsgException("moreElement",null); 203 } 204 } 205 206 /** 207 * Creates <code>FSNameIdentifierMappingResponse</code> object. 208 * This object is created by parsing the <code>XML</code> string. 209 * 210 * @param xml the <code>XML</code> string to be parse. 211 * @return the <code>FSNameIdentifierMappingResponse</code> object. 212 * @throws FSMsgException if there is an error in parsing the 213 * <code>XML</code> string. 214 */ 215 public static FSNameIdentifierMappingResponse parseXML(String xml) 216 throws FSMsgException { 217 Document doc = XMLUtils.toDOMDocument(xml, FSUtils.debug); 218 if (doc == null) { 219 FSUtils.debug.error("FSNameIdentifierMappingResponse.parseXML: " + 220 "error while parsing input xml string"); 221 throw new FSMsgException("parseError",null); 222 } 223 Element root = doc.getDocumentElement(); 224 return new FSNameIdentifierMappingResponse(root); 225 } 226 227 /** 228 * Returns the value of <code>ProviderID</code> attribute. 229 * 230 * @return the value of <code>ProviderID</code> attribute. 231 */ 232 public String getProviderID() { 233 return providerID; 234 } 235 236 /** 237 * Returns the <code>Status</code>. 238 * 239 * @return the <code>Status</code>. 240 */ 241 public Status getStatus() { 242 return status; 243 } 244 245 /** 246 * Returns the <code>NameIdentifier</code> object. This is the resulting 247 * mapped name identifier for the desired identity federation. 248 * 249 * @return <code>NameIdentifier</code> object, the resulting mapped name 250 * identifier for the desired identity federation 251 */ 252 public NameIdentifier getNameIdentifier() { 253 return nameIdentifier; 254 } 255 256 /** 257 * Sets the <code>MajorVersion</code> by parsing the version string. 258 * 259 * @param version a String representing the <code>MajorVersion</code> to 260 * be set. 261 * @throws FSMsgException on error. 262 */ 263 private void parseMajorVersion(String version) throws FSMsgException { 264 try { 265 majorVersion = Integer.parseInt(version); 266 } catch (NumberFormatException e) { 267 if (FSUtils.debug.messageEnabled()) { 268 FSUtils.debug.message("FSNameIdentifierMappingResponse." + 269 "parseMajorVersion:invalid MajorVersion:" + version, e); 270 } 271 throw new FSMsgException("wrongInput",null); 272 } 273 274 if (majorVersion != SAMLConstants.PROTOCOL_MAJOR_VERSION) { 275 if (majorVersion > SAMLConstants.PROTOCOL_MAJOR_VERSION) { 276 if (FSUtils.debug.messageEnabled()) { 277 FSUtils.debug.message("FSNameIdentifierMappingResponse." + 278 "parseMajorVersion: MajorVersion is too high"); 279 } 280 throw new FSMsgException("requestVersionTooHigh",null); 281 } else { 282 if (FSUtils.debug.messageEnabled()) { 283 FSUtils.debug.message("FSNameIdentifierMappingResponse." + 284 "parseMajorVersion: MajorVersion is too low"); 285 } 286 throw new FSMsgException("requestVersionTooLow",null); 287 } 288 } 289 } 290 291 /** 292 * Sets the <code>MinorVersion</code> by parsing the version string. 293 * 294 * @param minorVer a String representing the <code>MinorVersion</code> to 295 * be set. 296 * @throws FSMsgException when the version mismatchs. 297 */ 298 private void parseMinorVersion(String version) throws FSMsgException { 299 try { 300 minorVersion = Integer.parseInt(version); 301 } catch (NumberFormatException e) { 302 if (FSUtils.debug.messageEnabled()) { 303 FSUtils.debug.message("FSNameIdentifierMappingResponse." + 304 "parseMinorVersion:invalid MinorVersion:" + version, e); 305 } 306 throw new FSMsgException("wrongInput",null); 307 } 308 if (minorVersion > IFSConstants.FF_12_PROTOCOL_MINOR_VERSION) { 309 if (FSUtils.debug.messageEnabled()) { 310 FSUtils.debug.message("FSNameIdentifierMappingResponse." + 311 "parseMinorVersion: MinorVersion is too high"); 312 } 313 throw new FSMsgException("requestVersionTooHigh",null); 314 } else if (minorVersion < IFSConstants.FF_11_PROTOCOL_MINOR_VERSION) { 315 if (FSUtils.debug.messageEnabled()) { 316 FSUtils.debug.message("FSNameIdentifierMappingResponse." + 317 "parseMinorVersion: MinorVersion is too low"); 318 } 319 throw new FSMsgException("requestVersionTooLow",null); 320 } 321 } 322 323 /** 324 * Signs the XML document representing 325 * <code>NameIdentifierMappingResponse</code> using the certificate 326 * indicated by the property "com.sun.identity.saml.xmlsig.certalias" 327 * in AMConfig.properties file. 328 * 329 * @throws SAMLException if there is an error signing 330 * the <code>XML</code> string. 331 */ 332 public void signXML() throws SAMLException { 333 String certAlias = SystemConfigurationUtil.getProperty( 334 Constants.SAML_XMLSIG_CERT_ALIAS); 335 signXML(certAlias); 336 } 337 338 /** 339 * Signs the <code>XML</code> document representing 340 * <code>NameIdentifierMappingResponse</code> using the specified 341 * certificate. 342 * 343 * @param certAlias the alias/name of the certificate used for signing 344 * the XML document 345 * @throws SAMLException if there is an error signing 346 * the <code>XML</code> string or if the message is already 347 * signed. 348 */ 349 public void signXML(String certAlias) throws SAMLException { 350 FSUtils.debug.message("FSNameIdentifierMappingResponse.signXML"); 351 if (signed) { 352 if (FSUtils.debug.messageEnabled()) { 353 FSUtils.debug.message("FSNameIdentifierMappingResponse.signXML:" 354 + " the response is already signed."); 355 } 356 throw new SAMLResponderException(FSUtils.BUNDLE_NAME, 357 "alreadySigned",null); 358 } 359 if (certAlias == null || certAlias.length() < 1) { 360 if (FSUtils.debug.messageEnabled()) { 361 FSUtils.debug.message("FSNameIdentifierMappingResponse.signXML:" 362 + " null certAlias"); 363 } 364 throw new SAMLResponderException(FSUtils.BUNDLE_NAME, 365 "cannotFindCertAlias",null); 366 } 367 try { 368 XMLSignatureManager manager = XMLSignatureManager.getInstance(); 369 signatureString = manager.signXML(this.toXMLString(true, true), 370 certAlias, (String)null, 371 IFSConstants.RESPONSE_ID, 372 this.getResponseID(), false); 373 signature = XMLUtils.toDOMDocument(signatureString, FSUtils.debug) 374 .getDocumentElement(); 375 signed = true; 376 } catch (Exception e){ 377 FSUtils.debug.error("FSNameIdentifierMappingResponse.signXML: " + 378 "unable to sign", e); 379 throw new SAMLResponderException( 380 FSUtils.BUNDLE_NAME,"signFailed",null); 381 } 382 } 383 384 /** 385 * Returns the string representation of this object. 386 * 387 * @return An XML String representing the object. 388 * @throws FSMsgException on error. 389 */ 390 public String toXMLString() throws FSMsgException { 391 return toXMLString(true, true); 392 } 393 394 /** 395 * Returns a String representation of this object. 396 * 397 * @param includeNS : Determines whether or not the namespace qualifier 398 * is prepended to the Element when converted 399 * @param declareNS : Determines whether or not the namespace is declared 400 * within the Element. 401 * @return a string containing the valid XML for this element 402 * @throws FSMsgException if there is an error converting 403 * this object ot a string. 404 */ 405 406 public String toXMLString(boolean includeNS, boolean declareNS) 407 throws FSMsgException { 408 return toXMLString(includeNS, declareNS, false); 409 } 410 411 /** 412 * Returns a String representation of this object. 413 * 414 * @param includeNS Determines whether or not the namespace qualifier 415 * is prepended to the Element when converted 416 * @param declareNS Determines whether or not the namespace is declared 417 * within the Element. 418 * @param includeHeader Determines whether the output include the xml 419 * declaration header. 420 * @return a string containing the valid XML for this element 421 * @throws FSMsgException if there is an error converting 422 * this object ot a string. 423 */ 424 public String toXMLString(boolean includeNS, boolean declareNS, 425 boolean includeHeader) throws FSMsgException { 426 427 String prefixLIB = ""; 428 String uriLIB = ""; 429 String uriSAML = ""; 430 if (includeNS) { 431 prefixLIB = IFSConstants.LIB_PREFIX; 432 } 433 if (declareNS) { 434 uriLIB = IFSConstants.LIB_12_NAMESPACE_STRING; 435 uriSAML = IFSConstants.assertionDeclareStr; 436 } 437 String instantString = null; 438 try { 439 instantString = DateUtils.toUTCDateFormat(issueInstant); 440 } catch (Exception e) { 441 FSUtils.debug.error("FSNameIdentifierMappingResponse.toXMLString:" + 442 " could not convert issueInstant to String.", e); 443 } 444 // construct xml response 445 StringBuffer xml = new StringBuffer(1000); 446 if (includeHeader) { 447 xml.append(IFSConstants.XML_PREFIX) 448 .append(SAMLConstants.DEFAULT_ENCODING) 449 .append(IFSConstants.QUOTE) 450 .append(IFSConstants.SPACE) 451 .append(IFSConstants.QUESTION_MARK) 452 .append(IFSConstants.RIGHT_ANGLE) 453 .append(IFSConstants.NL); 454 } 455 xml.append(IFSConstants.LEFT_ANGLE) 456 .append(prefixLIB) 457 .append(IFSConstants.NAMEID_MAPPING_RESPONSE) 458 .append(uriLIB).append(uriSAML) 459 .append(IFSConstants.SPACE) 460 .append(IFSConstants.RESPONSE_ID) 461 .append(IFSConstants.EQUAL_TO) 462 .append(IFSConstants.QUOTE) 463 .append(responseID) 464 .append(IFSConstants.QUOTE) 465 .append(IFSConstants.SPACE) 466 .append(IFSConstants.IN_RESPONSE_TO) 467 .append(IFSConstants.EQUAL_TO) 468 .append(IFSConstants.QUOTE) 469 .append(inResponseTo) 470 .append(IFSConstants.QUOTE) 471 .append(IFSConstants.SPACE) 472 .append(IFSConstants.SPACE) 473 .append(IFSConstants.MAJOR_VERSION) 474 .append(IFSConstants.EQUAL_TO) 475 .append(IFSConstants.QUOTE) 476 .append(majorVersion) 477 .append(IFSConstants.EQUAL_TO) 478 .append(IFSConstants.QUOTE) 479 .append(IFSConstants.SPACE) 480 .append(IFSConstants.SPACE) 481 .append(IFSConstants.MINOR_VERSION) 482 .append(IFSConstants.EQUAL_TO) 483 .append(IFSConstants.QUOTE) 484 .append(minorVersion) 485 .append(IFSConstants.QUOTE) 486 .append(IFSConstants.SPACE) 487 .append(IFSConstants.SPACE) 488 .append(IFSConstants.ISSUE_INSTANT) 489 .append(IFSConstants.EQUAL_TO) 490 .append(instantString) 491 .append(IFSConstants.QUOTE) 492 .append(IFSConstants.SPACE) 493 .append(IFSConstants.RIGHT_ANGLE); 494 if (signed) { 495 if (signatureString != null) { 496 xml.append(signatureString); 497 } else if (signature != null) { 498 signatureString = XMLUtils.print(signature); 499 xml.append(signatureString); 500 } 501 } 502 xml.append(IFSConstants.LEFT_ANGLE) 503 .append(prefixLIB) 504 .append(IFSConstants.PROVIDER_ID) 505 .append(IFSConstants.RIGHT_ANGLE) 506 .append(providerID) 507 .append(IFSConstants.START_END_ELEMENT) 508 .append(prefixLIB) 509 .append(IFSConstants.PROVIDER_ID) 510 .append(IFSConstants.RIGHT_ANGLE) 511 .append(status.toString(includeNS, true)); 512 513 if (nameIdentifier != null) { 514 xml.append(nameIdentifier.toString()); 515 } 516 xml.append(IFSConstants.START_END_ELEMENT) 517 .append(prefixLIB) 518 .append(IFSConstants.NAMEID_MAPPING_RESPONSE) 519 .append(IFSConstants.RIGHT_ANGLE); 520 521 return xml.toString(); 522 } 523}