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: SAMLUtils.java,v 1.16 2010/01/09 19:41:06 qcheng Exp $ 026 * 027 * Portions Copyrighted 2012-2014 ForgeRock AS 028 */ 029 030package com.sun.identity.saml.common; 031 032import java.util.Collections; 033import java.util.Date; 034import java.util.Map; 035import java.util.Iterator; 036import java.util.List; 037import java.util.Set; 038import java.util.HashSet; 039import java.util.Enumeration; 040import java.util.ArrayList; 041import java.util.HashMap; 042import java.util.StringTokenizer; 043 044import java.text.StringCharacterIterator; 045import java.text.CharacterIterator; 046import java.io.UnsupportedEncodingException; 047import java.io.PrintWriter; 048import java.io.IOException; 049import java.io.ByteArrayInputStream; 050 051import java.security.MessageDigest; 052 053import java.net.URL; 054import java.net.MalformedURLException; 055 056import org.w3c.dom.*; 057 058import com.sun.identity.common.PeriodicGroupRunnable; 059import com.sun.identity.common.ScheduleableGroupAction; 060import com.sun.identity.common.SystemConfigurationUtil; 061import com.sun.identity.common.SystemConfigurationException; 062import com.sun.identity.common.SystemTimerPool; 063import com.sun.identity.common.TaskRunnable; 064import com.sun.identity.common.TimerPool; 065import com.sun.identity.shared.xml.XMLUtils; 066import com.sun.identity.shared.encode.URLEncDec; 067import com.sun.identity.shared.encode.Base64; 068 069import javax.servlet.ServletException; 070import javax.servlet.http.HttpServletResponse; 071import javax.servlet.http.HttpServletRequest; 072 073import javax.xml.soap.MimeHeaders; 074import javax.xml.soap.MimeHeader; 075 076import com.sun.identity.saml.assertion.SubjectConfirmation; 077import com.sun.identity.saml.assertion.Assertion; 078import com.sun.identity.saml.assertion.Attribute; 079import com.sun.identity.saml.assertion.AttributeStatement; 080import com.sun.identity.saml.assertion.AuthenticationStatement; 081import com.sun.identity.saml.assertion.AudienceRestrictionCondition; 082import com.sun.identity.saml.assertion.Condition; 083import com.sun.identity.saml.assertion.Conditions; 084import com.sun.identity.saml.assertion.Statement; 085import com.sun.identity.saml.assertion.SubjectStatement; 086import com.sun.identity.saml.xmlsig.XMLSignatureManager; 087import com.sun.identity.saml.plugins.PartnerAccountMapper; 088import com.sun.identity.saml.protocol.*; 089import com.sun.identity.saml.servlet.POSTCleanUpRunnable; 090import com.sun.identity.plugin.session.SessionException; 091import com.sun.identity.plugin.session.SessionManager; 092import com.sun.identity.plugin.session.SessionProvider; 093import com.sun.identity.saml.assertion.Subject; 094import com.sun.identity.saml.SAMLClient; 095import com.sun.identity.federation.common.FSUtils; 096 097import javax.xml.parsers.DocumentBuilder; 098 099import org.apache.xml.security.c14n.Canonicalizer; 100 101/** 102 * This class contains some utility methods for processing SAML protocols. 103 * 104 * @supported.api 105 */ 106public class SAMLUtils extends SAMLUtilsCommon { 107 108 /** 109 * Attribute that specifies maximum content length for SAML request in 110 * <code>AMConfig.properties</code> file. 111 */ 112 public static final String HTTP_MAX_CONTENT_LENGTH = 113 "com.sun.identity.saml.request.maxContentLength"; 114 115 /** 116 * Default maximum content length is set to 16k. 117 */ 118 public static final int defaultMaxLength = 16384; 119 120 /** 121 * Default maximum content length in string format. 122 */ 123 public static final String DEFAULT_CONTENT_LENGTH = 124 String.valueOf(defaultMaxLength); 125 126 private static final String ERROR_JSP = "/saml2/jsp/autosubmittingerror.jsp"; 127 128 private static int maxContentLength = 0; 129 private static Map idTimeMap = Collections.synchronizedMap(new HashMap()); 130 private static TaskRunnable cGoThrough = null; 131 private static TaskRunnable cPeriodic = null; 132 private static Object ssoToken; 133 134 static { 135 org.apache.xml.security.Init.init(); 136 if (SystemConfigurationUtil.isServerMode()) { 137 long period = ((Integer) SAMLServiceManager.getAttribute( 138 SAMLConstants.CLEANUP_INTERVAL_NAME)).intValue() * 1000; 139 cGoThrough = new POSTCleanUpRunnable(period, idTimeMap); 140 TimerPool timerPool = SystemTimerPool.getTimerPool(); 141 timerPool.schedule(cGoThrough, new Date(((System.currentTimeMillis() 142 + period) / 1000) * 1000)); 143 ScheduleableGroupAction periodicAction = new 144 ScheduleableGroupAction() { 145 public void doGroupAction(Object obj) { 146 idTimeMap.remove(obj); 147 } 148 }; 149 cPeriodic = new PeriodicGroupRunnable(periodicAction, period, 150 180000, true); 151 timerPool.schedule(cPeriodic, new Date(((System.currentTimeMillis() + 152 period) / 1000) * 1000)); 153 } 154 try { 155 maxContentLength = Integer.parseInt(SystemConfigurationUtil. 156 getProperty(SAMLUtils.HTTP_MAX_CONTENT_LENGTH, 157 SAMLUtils.DEFAULT_CONTENT_LENGTH)); 158 } catch (NumberFormatException ne) { 159 SAMLUtils.debug.error("Wrong format of SAML request max content " 160 + "length. Take default value."); 161 maxContentLength= SAMLUtils.defaultMaxLength; 162 } 163 } 164 165 /** 166 * Constructor 167 * iPlanet-PRIVATE-DEFAULT-CONSTRUCTOR 168 */ 169 private SAMLUtils() { 170 } 171 172 173 /** 174 * Generates an ID String with length of SAMLConstants.ID_LENGTH. 175 * @return string the ID String; or null if it fails. 176 */ 177 public static String generateAssertionID() { 178 String encodedID = generateID(); 179 if (encodedID == null) { 180 return null; 181 } 182 183 String id = null; 184 try { 185 id = SystemConfigurationUtil.getServerID( 186 SAMLServiceManager.getServerProtocol(), 187 SAMLServiceManager.getServerHost(), 188 Integer.parseInt(SAMLServiceManager.getServerPort()), 189 SAMLServiceManager.getServerURI()); 190 } catch (Exception ex) { 191 if (SAMLUtils.debug.messageEnabled()) { 192 SAMLUtils.debug.message("SAMLUtil:generateAssertionID: " 193 + "exception obtain serverID:", ex); 194 } 195 } 196 if (id == null) { 197 return encodedID; 198 } else { 199 return (encodedID + id); 200 } 201 } 202 203 /** 204 * Verifies if an element is a type of a specific query. 205 * Currently, this method is used by class AuthenticationQuery, 206 * AuthorizationDecisionQuery, and AttributeQuery. 207 * @param element a DOM Element which needs to be verified. 208 * @param queryname A specific name of a query, for example, 209 * AuthenticationQuery, AuthorizationDecisionQuery, or 210 * AttributeQuery. 211 * @return true if the element is a type of the specified query; false 212 * otherwise. 213 */ 214 public static boolean checkQuery(Element element, String queryname) { 215 String tag = element.getLocalName(); 216 if (tag == null) { 217 return false; 218 } else if (tag.equals("Query") || tag.equals("SubjectQuery")) { 219 NamedNodeMap nm = element.getAttributes(); 220 int len = nm.getLength(); 221 String attrName; 222 Attr attr; 223 boolean found = false; 224 for (int j = 0; j < len; j++) { 225 attr = (Attr) nm.item(j); 226 attrName = attr.getLocalName(); 227 if ((attrName != null) && (attrName.equals("type")) && 228 (attr.getNodeValue().equals(queryname + "Type"))) { 229 found = true; 230 break; 231 } 232 } 233 if (!found) { 234 return false; 235 } 236 } else if (!tag.equals(queryname)) { 237 return false; 238 } 239 return true; 240 } 241 242 /** 243 * Generates sourceID of a site. 244 * @param siteURL a String that uniquely identifies a site. 245 * @return <code>Base64</code> encoded SHA digest of siteURL. 246 */ 247 public static String generateSourceID(String siteURL) { 248 if ((siteURL == null) || (siteURL.length() == 0)) { 249 SAMLUtils.debug.error("SAMLUtils.genrateSourceID: empty siteURL."); 250 return null; 251 } 252 MessageDigest md = null; 253 try { 254 md = MessageDigest.getInstance("SHA"); 255 } catch (Exception e) { 256 SAMLUtils.debug.error("SAMLUtils.generateSourceID: Exception when" 257 + " generating digest:",e); 258 return null; 259 } 260 md.update(SAMLUtils.stringToByteArray(siteURL)); 261 byte byteResult[] = md.digest(); 262 String result = null; 263 try { 264 result = Base64.encode(byteResult).trim(); 265 } catch (Exception e) { 266 SAMLUtils.debug.error("SAMLUtils.generateSourceID: Exception:",e); 267 } 268 return result; 269 } 270 271 /** 272 * Generates assertion handle. 273 * @return 20-byte random string to be used to form an artifact. 274 */ 275 public static String generateAssertionHandle() { 276 if (random == null) { 277 return null; 278 } 279 byte bytes[] = new byte[SAMLConstants.ID_LENGTH]; 280 random.nextBytes(bytes); 281 String id = null; 282 try { 283 id = SystemConfigurationUtil.getServerID( 284 SAMLServiceManager.getServerProtocol(), 285 SAMLServiceManager.getServerHost(), 286 Integer.parseInt(SAMLServiceManager.getServerPort()), 287 SAMLServiceManager.getServerURI()); 288 } catch (Exception ex) { 289 if (SAMLUtils.debug.messageEnabled()) { 290 SAMLUtils.debug.message("SAMLUtil:generateAssertionHandle: " 291 + "exception obtain serverID:", ex); 292 } 293 } 294 if (id != null) { 295 byte idBytes[] = stringToByteArray(id); 296 // TODO: should we check if idBytes.length == 2 ? 297 if (idBytes.length < bytes.length) { 298 for (int i = 1; i <= idBytes.length; i++) { 299 bytes[bytes.length - i] = idBytes[idBytes.length - i]; 300 } 301 } 302 } 303 return byteArrayToString(bytes); 304 } 305 306 /** 307 * Converts a HEX encoded string to a byte array. 308 * @param hexString HEX encoded string 309 * @return byte array. 310 */ 311 public static byte[] hexStringToByteArray(String hexString) { 312 int read = hexString.length(); 313 byte[] byteArray = new byte[read/2]; 314 for (int i=0, j=0; i < read; i++, j++) { 315 String part = hexString.substring(i,i+2); 316 byteArray[j] = 317 new Short(Integer.toString(Integer.parseInt(part,16))). 318 byteValue(); 319 i++; 320 } 321 return byteArray; 322 } 323 324 /** 325 * Converts HEX encoded string to Base64 encoded string. 326 * @param hexString HEX encoded string. 327 * @return Base64 encoded string. 328 */ 329 public static String hexStringToBase64(String hexString) { 330 int read = hexString.length(); 331 byte[] byteArray = new byte[read/2]; 332 for (int i=0, j=0; i < read; i++, j++) { 333 String part = hexString.substring(i,i+2); 334 byteArray[j] = 335 new Short(Integer.toString(Integer.parseInt(part,16))). 336 byteValue(); 337 i++; 338 } 339 String encodedID = null; 340 try { 341 encodedID = Base64.encode(byteArray).trim(); 342 } catch (Exception e) { 343 if (SAMLUtils.debug.messageEnabled()) { 344 SAMLUtils.debug.message("SAMLUtil:hexStringToBase64: " 345 + "exception encode input:", e); 346 } 347 } 348 if (SAMLUtils.debug.messageEnabled()) { 349 SAMLUtils.debug.message("base 64 source id is :"+encodedID); 350 } 351 return encodedID; 352 } 353 354 /** 355 * Gets sourceSite corresponding to an issuer from the partner URL list. 356 * @param issuer The issuer string. 357 * @return SAMLServiceManager.SOAPEntry of the issuer if it's on the list; 358 * null otherwise. 359 */ 360 public static SAMLServiceManager.SOAPEntry getSourceSite(String issuer) { 361 if (issuer == null) { 362 return null; 363 } 364 Map entries = (Map) SAMLServiceManager.getAttribute( 365 SAMLConstants.PARTNER_URLS); 366 if (entries == null) { 367 SAMLUtils.debug.error("SAMLUtils.isOnPartnerURLList: PartnerURL " 368 + "list is null."); 369 return null; 370 } 371 372 Iterator entryIter = entries.values().iterator(); 373 boolean found = false; 374 SAMLServiceManager.SOAPEntry srcSite = null; 375 String theIssuer = null; 376 while (entryIter.hasNext()) { 377 srcSite = (SAMLServiceManager.SOAPEntry) entryIter.next(); 378 if ((srcSite != null) && 379 ((theIssuer = srcSite.getIssuer()) != null) && 380 (theIssuer.equals(issuer))) { 381 found = true; 382 break; 383 } 384 } 385 if (found) { 386 return srcSite; 387 } else { 388 return null; 389 } 390 } 391 392 /** 393 * Returns site ID based on the host name. The site ID 394 * will be in Base64 encoded format. This method will print out site ID 395 * to the standard output 396 * @param args host name 397 */ 398 public static void main(String args[]) { 399 400 if (args.length != 1) { 401 System.out.println("usage : java SAMLUtils <host_name>"); 402 return; 403 } 404 405 System.out.println(generateSourceID(args[0])); 406 } 407 408 /** 409 * Checks if a <code>SubjectConfirmation</code> is correct. 410 * @param sc <code>SubjectConfirmation</code> instance to be checked. 411 * @return true if the <code>SubjectConfirmation</code> instance passed in 412 * has only one <code>ConfirmationMethod</code>, and this 413 * <code>ConfirmationMethod</code> is set to 414 * <code>SAMLConstants.CONFIRMATION_METHOD_IS</code>. 415 */ 416 public static boolean isCorrectConfirmationMethod(SubjectConfirmation sc) { 417 if (sc == null) { 418 return false; 419 } 420 421 Set cmSet = sc.getConfirmationMethod(); 422 if ((cmSet == null) || (cmSet.size() != 1)) { 423 if (SAMLUtils.debug.messageEnabled()) { 424 SAMLUtils.debug.message("SAMLUtils.isCorrectConfirmationMethod:" 425 + " missing ConfirmationMethod in the Subject."); 426 } 427 return false; 428 } 429 430 String conMethod = (String) cmSet.iterator().next(); 431 if ((conMethod == null) || 432 (!conMethod.equals(SAMLConstants.CONFIRMATION_METHOD_IS))) { 433 if (SAMLUtils.debug.messageEnabled()) { 434 SAMLUtils.debug.message("SAMLUtils.isCorrectConfirmationMethod:" 435 + " wrong ConfirmationMethod value."); 436 } 437 return false; 438 } 439 440 return true; 441 } 442 443 /** 444 * Returns true if the assertion is valid both time wise and 445 * signature wise, and contains at least one AuthenticationStatement. 446 * @param assertion <code>Assertion</code> instance to be checked. 447 * @return <code>true</code> if the assertion is valid both time wise and 448 * signature wise, and contains at least one AuthenticationStatement. 449 */ 450 public static boolean isAuthNAssertion(Assertion assertion) { 451 if (assertion == null) { 452 return false; 453 } 454 455 if ((!assertion.isTimeValid()) || (!assertion.isSignatureValid())) { 456 return false; 457 } 458 459 Set statements = assertion.getStatement(); 460 Statement statement = null; 461 Iterator iterator = statements.iterator(); 462 while (iterator.hasNext()) { 463 statement = (Statement) iterator.next(); 464 if (statement.getStatementType() == 465 Statement.AUTHENTICATION_STATEMENT) { 466 return true; 467 } 468 } // loop through statements 469 return false; 470 } 471 472 /** 473 * Converts a string to a byte array. 474 * @param input a String to be converted. 475 * @return result byte array. 476 */ 477 public static byte[] stringToByteArray(String input) { 478 char chars[] = input.toCharArray(); 479 byte bytes[] = new byte[chars.length]; 480 for (int i = 0; i < chars.length; i++) { 481 bytes[i] = (byte) chars[i]; 482 } 483 return bytes; 484 } 485 486 487 /** 488 * Returns server ID. 489 * @param idTypeString An ID string 490 * @return server ID part of the id. 491 */ 492 public static String getServerID(String idTypeString) { 493 if (idTypeString == null) { 494 return null; 495 } 496 int len = idTypeString.length(); 497 String id = null; 498 if (len >= SAMLConstants.SERVER_ID_LENGTH) { 499 id = idTypeString.substring((len - SAMLConstants.SERVER_ID_LENGTH), 500 len); 501 return id; 502 } else { 503 return null; 504 } 505 } 506 507 /** 508 * Returns server url of a site. 509 * @param str Server ID. 510 * @return Server url corresponding to the server id. 511 */ 512 public static String getServerURL(String str) { 513 String id = SAMLUtils.getServerID(str); 514 if (id == null) { 515 return null; 516 } 517 518 if (SAMLUtils.debug.messageEnabled()) { 519 SAMLUtils.debug.message("SAMLUtils.getServerURL: id=" + id); 520 } 521 522 String remoteUrl = null; 523 try { 524 remoteUrl = SystemConfigurationUtil.getServerFromID(id); 525 } catch (SystemConfigurationException se) { 526 if (SAMLUtils.debug.messageEnabled()) { 527 SAMLUtils.debug.message("SAMLUtils.getServerURL: ServerEntry" + 528 "NotFoundException for " + id); 529 } 530 return null; 531 } 532 String thisUrl = SAMLServiceManager.getServerURL(); 533 if (SAMLUtils.debug.messageEnabled()) { 534 SAMLUtils.debug.message("SAMLUtils.getServerURL: remoteUrl=" + 535 remoteUrl + ", thisUrl=" + thisUrl); 536 } 537 if ((remoteUrl == null) || (thisUrl == null) || 538 (remoteUrl.equalsIgnoreCase(thisUrl))) { 539 return null; 540 } else { 541 return remoteUrl; 542 } 543 } 544 545 /** 546 * Returns full service url. 547 * @param shortUrl short URL of the service. 548 * @return full service url. 549 */ 550 public static String getFullServiceURL(String shortUrl) { 551 String result = null; 552 try { 553 URL u = new URL(shortUrl); 554 URL weburl = SystemConfigurationUtil.getServiceURL( 555 SAMLConstants.SAML_AM_NAMING, u.getProtocol(), u.getHost(), 556 u.getPort(), u.getPath()); 557 result = weburl.toString(); 558 if (SAMLUtils.debug.messageEnabled()) { 559 SAMLUtils.debug.message("SAMLUtils.getFullServiceURL:" + 560 "full remote URL is: " + result); 561 } 562 } catch (Exception e) { 563 if (SAMLUtils.debug.warningEnabled()) { 564 SAMLUtils.debug.warning("SAMLUtils.getFullServiceURL:" + 565 "Exception:", e); 566 } 567 } 568 return result; 569 } 570 571 /** 572 * Returns attributes included in <code>AttributeStatement</code> of the 573 * assertion. 574 * @param envParameters return map which includes name value pairs of 575 * attributes included in <code>AttributeStatement</code> of the assertion 576 * @param assertion an <code>Assertion</code> object which contains 577 * <code>AttributeStatement</code> 578 * @param subject the <code>Subject</code> instance from 579 * <code>AuthenticationStatement</code>. The <code>Subject</code> 580 * included in <code>AttributeStatement</code> must match this 581 * <code>Subject</code> instance. 582 */ 583 public static void addEnvParamsFromAssertion(Map envParameters, 584 Assertion assertion, 585 com.sun.identity.saml.assertion.Subject subject) { 586 Set statements = assertion.getStatement(); 587 Statement statement = null; 588 Iterator stmtIter = null; 589 List attrs = null; 590 Iterator attrIter = null; 591 Attribute attribute = null; 592 Element attrValue = null; 593 List attrValues = null; 594 String attrName = null; 595 String attrValueString = null; 596 if ((statements != null) && (!statements.isEmpty())) { 597 stmtIter = statements.iterator(); 598 while (stmtIter.hasNext()) { 599 statement = (Statement) stmtIter.next(); 600 if (statement.getStatementType() == 601 Statement.ATTRIBUTE_STATEMENT) { 602 // check for subject 603 if (!subject.equals( 604 ((AttributeStatement)statement).getSubject())) { 605 continue; 606 } 607 608 attrs = ((AttributeStatement) statement).getAttribute(); 609 attrIter = attrs.iterator(); 610 while (attrIter.hasNext()) { 611 attribute = (Attribute) attrIter.next(); 612 try { 613 attrValues = attribute.getAttributeValue(); 614 } catch (Exception e) { 615 debug.error("SAMLUtils.addEnvParamsFromAssertion:"+ 616 " cannot obtain attribute value:", e); 617 continue; 618 } 619 attrName = attribute.getAttributeName(); 620 List attrValueList = null; 621 622 for(Iterator avIter = attrValues.iterator(); 623 avIter.hasNext(); ) { 624 625 attrValue = (Element) avIter.next(); 626 if (!XMLUtils.hasElementChild(attrValue)) { 627 attrValueString = 628 XMLUtils.getElementValue(attrValue); 629 if (attrValueList == null) { 630 attrValueList = new ArrayList(); 631 } 632 attrValueList.add(attrValueString); 633 } 634 } 635 if (attrValueList != null) { 636 if (debug.messageEnabled()) { 637 debug.message( 638 "SAMLUtils.addEnvParamsFromAssertion:" + 639 " attrName = " + attrName + 640 " attrValue = " + attrValueList); 641 } 642 String[] attrValueStrs = (String[])attrValueList. 643 toArray(new String[attrValueList.size()]); 644 try { 645 envParameters.put(attrName, attrValueStrs); 646 } catch (Exception ex) { 647 if (debug.messageEnabled()) { 648 debug.message( 649 "SAMLUtils.addEnvParamsFromAssertion:", 650 ex); 651 } 652 } 653 } else if (debug.messageEnabled()) { 654 if (debug.messageEnabled()) { 655 debug.message( 656 "SAMLUtils.addEnvParamsFromAssertion:" + 657 " attrName = " + attrName + 658 " has no value"); 659 } 660 } 661 } 662 } // if it's an attribute statement 663 } 664 } 665 } 666 667 /** 668 * Returns maximum content length of a SAML request. 669 * @return maximum content length of a SAML request. 670 */ 671 public static int getMaxContentLength() { 672 return maxContentLength; 673 } 674 675 // ************************************************************************ 676 // Methods used by SAML Servlets 677 // ************************************************************************ 678 679 /** 680 * Checks content length of a http request to avoid dos attack. 681 * In case SAML inter-op with other SAML vendor who may not provide content 682 * length in HttpServletRequest. We decide to support no length restriction 683 * for Http communication. Here, we use a special value (e.g. 0) to 684 * indicate that no enforcement is required. 685 * @param request <code>HttpServletRequest</code> instance to be checked. 686 * @exception ServletException if context length of the request exceeds 687 * maximum content length allowed. 688 */ 689 public static void checkHTTPContentLength(HttpServletRequest request) 690 throws ServletException { 691 if (maxContentLength != 0) { 692 int length = request.getContentLength(); 693 if (SAMLUtils.debug.messageEnabled()) { 694 SAMLUtils.debug.message("HttpRequest content length= " +length); 695 } 696 if (length > maxContentLength) { 697 if (SAMLUtils.debug.messageEnabled()) { 698 SAMLUtils.debug.message( 699 "content length too large" + length); 700 } 701 throw new ServletException( 702 SAMLUtils.bundle.getString("largeContentLength")); 703 } 704 } 705 } 706 707 /** 708 * Post assertions and attributes to the target url. 709 * This method opens a URL connection to the target specified and POSTs 710 * assertions to it using the passed HttpServletResponse object. It POSTs 711 * multiple parameter names "assertion" with value being each of the 712 * <code>Assertion</code> in the passed Set. 713 * @param response <code>HttpServletResponse</code> object 714 * @param out The print writer which for content is to be written too. 715 * @param assertion List of <code>Assertion</code>s to be posted. 716 * @param targeturl target url 717 * @param attrMap Map of attributes to be posted to the target 718 */ 719 public static void postToTarget(HttpServletResponse response, PrintWriter out, 720 List assertion, String targeturl, Map attrMap) throws IOException { 721 out.println("<HTML>"); 722 out.println("<HEAD>\n"); 723 out.println("<TITLE>Access rights validated</TITLE>\n"); 724 out.println("</HEAD>\n"); 725 out.println("<BODY Onload=\"document.forms[0].submit()\">"); 726 Iterator it = null; 727 if (SAMLUtils.debug.messageEnabled()) { 728 out.println("<H1>Access rights validated</H1>\n"); 729 out.println("<meta http-equiv=\"refresh\" content=\"20\">\n"); 730 out.println("<P>We have verified your access rights <STRONG>" + 731 "</STRONG> according to the assertion shown " 732 +"below. \n"); 733 out.println("You are being redirected to the resource.\n"); 734 out.println("Please wait ......\n"); 735 out.println("</P>\n"); 736 out.println("<HR><P>\n"); 737 if (assertion != null) { 738 it = assertion.iterator(); 739 while (it.hasNext()) { 740 out.println(SAMLUtils.displayXML((String)it.next())); 741 } 742 } 743 out.println("</P>\n"); 744 } 745 out.println("<FORM METHOD=\"POST\" ACTION=\"" + targeturl + "\">"); 746 if (assertion != null) { 747 it = assertion.iterator(); 748 while (it.hasNext()) { 749 out.println("<INPUT TYPE=\"HIDDEN\" NAME=\""+ 750 SAMLConstants.POST_ASSERTION_NAME + "\""); 751 out.println("VALUE=\"" + 752 URLEncDec.encode((String)it.next()) + "\">"); 753 } 754 } 755 if (attrMap != null && !attrMap.isEmpty()) { 756 StringBuffer attrNamesSB = new StringBuffer(); 757 Set entrySet = attrMap.entrySet(); 758 for(Iterator iter = entrySet.iterator(); iter.hasNext();) { 759 Map.Entry entry = (Map.Entry)iter.next(); 760 String attrName = HTMLEncode((String)entry.getKey(), '\"'); 761 String attrValue = HTMLEncode((String)entry.getValue(), '\"'); 762 out.println("<INPUT TYPE=\"HIDDEN\" NAME=\""+ attrName + 763 "\" VALUE=\"" + attrValue + "\">"); 764 if (attrNamesSB.length() > 0) { 765 attrNamesSB.append(":"); 766 } 767 attrNamesSB.append(attrName); 768 } 769 out.println("<INPUT TYPE=\"HIDDEN\" NAME=\""+ 770 SAMLConstants.POST_ATTR_NAMES + "\" VALUE=\"" + 771 attrNamesSB + "\">"); 772 } 773 out.println("</FORM>"); 774 out.println("</BODY></HTML>"); 775 out.close(); 776 } 777 778 /** 779 * Returns true of false based on whether the target passed as parameter 780 * accepts form POST. 781 * @param targetIn url to be checked 782 * @return true if it should post assertion to the target passed in; false 783 * otherwise. 784 */ 785 public static boolean postYN(String targetIn) { 786 SAMLUtils.debug.message("Inside postYN()"); 787 if ((targetIn == null) || (targetIn.length() == 0)) { 788 return false; 789 } 790 Set targets = (Set) SAMLServiceManager. 791 getAttribute(SAMLConstants.POST_TO_TARGET_URLS); 792 if ((targets == null) || (targets.size() == 0)) { 793 return false; 794 } 795 URL targetUrl = null; 796 try { 797 targetUrl = new URL(targetIn); 798 } catch (MalformedURLException me ) { 799 SAMLUtils.debug.error("SAMLUtils:postYN(): Malformed URL passed"); 800 return false; 801 } 802 String targetInHost = targetUrl.getHost(); 803 int targetInPort = targetUrl.getPort(); 804 String targetInPath = targetUrl.getPath(); 805 // making target string without protocol 806 String targetToCompare = new StringBuffer(targetInHost.toLowerCase()) 807 .append(":").append(String.valueOf(targetInPort)) 808 .append("/").append(targetInPath).toString(); 809 if (targets.contains(targetToCompare)) { 810 return true; 811 } else { 812 return false; 813 } 814 } 815 816 /** 817 * Replaces every occurence of ch with 818 * "&#<ascii code of ch>;" 819 * @param srcStr orginal string to to be encoded. 820 * @param ch the charactor needs to be encoded. 821 * @return encoded string 822 */ 823 public static String HTMLEncode(String srcStr, char ch) { 824 if (srcStr == null) { 825 return null; 826 } 827 828 int fromIndex = 0; 829 int toIndex; 830 StringBuffer dstSB = new StringBuffer(); 831 832 while((toIndex = srcStr.indexOf(ch, fromIndex)) != -1) { 833 dstSB.append(srcStr.substring(fromIndex, toIndex)) 834 .append("&#" + (int)ch + ";"); 835 fromIndex = toIndex + 1; 836 } 837 dstSB.append(srcStr.substring(fromIndex)); 838 839 return dstSB.toString(); 840 } 841 842 /** 843 * Displays an XML string. 844 * This is a utility function used to hack up an HTML display of an XML 845 * string. 846 * @param input original string 847 * @return encoded string so it can be displayed properly by html. 848 */ 849 public static String displayXML(String input) { 850 debug.message("In displayXML "); 851 StringCharacterIterator iter = new StringCharacterIterator(input); 852 StringBuffer buf = new StringBuffer(); 853 854 for(char c = iter.first();c != CharacterIterator.DONE;c = iter.next()) { 855 if (c=='>') { 856 buf.append(">"); 857 } else if (c=='<') { 858 buf.append("<"); 859 } else if (c=='\n'){ 860 buf.append("<BR>\n"); 861 } else { 862 buf.append(c); 863 } 864 } 865 return buf.toString(); 866 } 867 868 /** 869 * Gets the list of <code>Assertion</code> objects from a list of 870 * 'String' assertions. 871 * @param assertions List of assertions in string format 872 * @return List of <code>Assertion</code> objects 873 */ 874 public static List getListOfAssertions(List assertions) { 875 List returnAssertions = new ArrayList(); 876 try { 877 if (assertions != null) { 878 Iterator it = assertions.iterator(); 879 while (it.hasNext()) { 880 Document doc = XMLUtils.toDOMDocument((String)it.next(), 881 debug); 882 Element root = doc.getDocumentElement(); 883 if (root != null) { 884 Assertion assertion = new Assertion(root); 885 returnAssertions.add(assertion); 886 } 887 } 888 } 889 } catch (Exception e) { 890 if (debug.messageEnabled()) { 891 debug.message("SAMLUtils.getListOfAssertions : " + 892 "Exception : ", e); 893 } 894 } 895 return returnAssertions; 896 } 897 898 899 // ************************************************************************ 900 // Methods used / shared by SAML Authentication Module and SAML Servlets 901 // ************************************************************************ 902 903 /** 904 * Returns byte array from a SAML <code>Response</code>. 905 * @param samlResponse <code>Response</code> object 906 * @return byte array 907 * @exception SAMLException if error occurrs during the process. 908 */ 909 public static byte[] getResponseBytes(Response samlResponse) 910 throws SAMLException 911 { 912 byte ret[] = null; 913 try { 914 ret = samlResponse.toString(true, true, true). 915 getBytes(SAMLConstants.DEFAULT_ENCODING); 916 } catch (UnsupportedEncodingException ue) { 917 if (debug.messageEnabled()) { 918 debug.message("getResponseBytes : " , ue); 919 } 920 throw new SAMLException(ue.getMessage()); 921 } 922 return ret; 923 } 924 925 /** 926 * Returns <code>Response</code> object from byte array. 927 * @param bytes byte array 928 * @return <code>Response</code> object 929 */ 930 public static Response getResponse(byte [] bytes) { 931 Response temp = null; 932 if (bytes == null) { 933 return null; 934 } 935 try { 936 temp = Response.parseXML(new ByteArrayInputStream(bytes)); 937 } catch (SAMLException se) { 938 debug.error("getResponse : " , se); 939 } 940 return temp; 941 } 942 943 /** 944 * Verifies a <code>Response</code>. 945 * @param response SAML <code>Response</code> object 946 * @param requestUrl this server's POST profile URL 947 * @param request <code>HttpServletRequest</code> object 948 * @return true if the response is valid; false otherwise. 949 */ 950 public static boolean verifyResponse(Response response, 951 String requestUrl, HttpServletRequest request) { 952 if (!response.isSignatureValid()) { 953 debug.message("verifyResponse: Response's signature is invalid."); 954 return false; 955 } 956 957 // check Recipient == this server's POST profile URL(requestURL) 958 String recipient = response.getRecipient(); 959 if ((recipient == null) || (recipient.length() == 0) || 960 ((!equalURL(recipient, requestUrl)) && 961 (!equalURL(recipient,getLBURL(requestUrl, request))))) { 962 debug.error("verifyResponse : Incorrect Recipient."); 963 return false; 964 } 965 966 // check status of the Response 967 if (!response.getStatus().getStatusCode().getValue().endsWith( 968 SAMLConstants.STATUS_CODE_SUCCESS_NO_PREFIX)) { 969 debug.error("verifyResponse : Incorrect StatusCode value."); 970 return false; 971 } 972 973 return true; 974 } 975 976 private static String getLBURL(String requestUrl, 977 HttpServletRequest request) 978 { 979 String host = request.getHeader("host"); 980 if (host == null) { 981 return requestUrl; 982 } 983 int index = requestUrl.indexOf("//"); 984 if (index == -1) { 985 return requestUrl; 986 } 987 StringBuffer sb = new StringBuffer(200); 988 sb.append(requestUrl.substring(0, index + 2)).append(host); 989 String rest = requestUrl.substring(index +2, requestUrl.length()); 990 if ((index = rest.indexOf("/")) != -1) { 991 sb.append(rest.substring(index, rest.length())); 992 } 993 if (debug.messageEnabled()) { 994 debug.message("getLBURL: LBURL = " + sb.toString()); 995 } 996 return sb.toString().trim(); 997 } 998 999 // ************************************************************************ 1000 // Methods used by SAML Authentication Module 1001 // ************************************************************************ 1002 1003 /** 1004 * Gets List of assertions in String format from a list of 1005 * <code>Assertion</code> objects. 1006 * @param assertions List of <code>Assertion</code> objects. 1007 * @return List of assertions in String format 1008 */ 1009 public static List getStrAssertions(List assertions) { 1010 List returnAssertions = new ArrayList(); 1011 if (assertions != null) { 1012 Iterator it = assertions.iterator(); 1013 while (it.hasNext()) { 1014 returnAssertions.add( 1015 ((Assertion)(it.next())).toString(true,true)); 1016 } 1017 } 1018 return returnAssertions; 1019 } 1020 1021 /** 1022 * Verifies Signature for Post response. 1023 * @param samlResponse <code>Response</code> object from post profile. 1024 * @return true if the signature on the reponse is valid; false otherwise. 1025 */ 1026 public static boolean verifySignature(Response samlResponse) { 1027 if ((samlResponse != null) && 1028 (!samlResponse.isSigned() || (!samlResponse.isSignatureValid()))) { 1029 return false; 1030 } 1031 return true; 1032 } 1033 1034 /** 1035 * Gets Attribute Map to be set in the Session. 1036 * @param partnerdest <code>SOAPEntry</code> object 1037 * @param assertions List of <code>Assertion</code>s 1038 * @param subject <code>Subject</code> object 1039 * @param target target of final SSO 1040 * @return Map which contains name and attributes. 1041 * @exception Exception if an error occurrs. 1042 */ 1043 public static Map getAttributeMap( 1044 SAMLServiceManager.SOAPEntry partnerdest, 1045 List assertions, 1046 com.sun.identity.saml.assertion.Subject subject, 1047 String target) 1048 throws Exception { 1049 String srcID = partnerdest.getSourceID(); 1050 String name = null; 1051 String org = null; 1052 Map attrMap = new HashMap(); 1053 PartnerAccountMapper paMapper = partnerdest.getPartnerAccountMapper(); 1054 1055 if (paMapper != null) { 1056 Map map = paMapper.getUser(assertions, srcID, target); 1057 name = (String) map.get(PartnerAccountMapper.NAME); 1058 org = (String) map.get(PartnerAccountMapper.ORG); 1059 attrMap = (Map) map.get(PartnerAccountMapper.ATTRIBUTE); 1060 } 1061 1062 if (attrMap == null) { 1063 attrMap = new HashMap(); 1064 } 1065 attrMap.put(SAMLConstants.USER_NAME, name); 1066 if ((org != null) && (org.length() != 0)) { 1067 attrMap.put(SessionProvider.REALM, org); 1068 } else { 1069 attrMap.put(SessionProvider.REALM, "/"); 1070 } 1071 1072 if (debug.messageEnabled()) { 1073 debug.message("getAttributeMap : " + "name = " + 1074 name + ", realm=" + org + ", attrMap = " + attrMap); 1075 } 1076 1077 return attrMap; 1078 } 1079 1080 /** 1081 * Checks response and get back a Map of relevant data including, 1082 * Subject, SOAPEntry for the partner and the List of Assertions. 1083 * @param response <code>Response</code> object 1084 * @return Map of data including Subject, SOAPEntry, and list of assertions. 1085 */ 1086 public static Map verifyAssertionAndGetSSMap(Response response) { 1087 // loop to check assertions 1088 com.sun.identity.saml.assertion.Subject subject = null; 1089 SAMLServiceManager.SOAPEntry srcSite = null; 1090 List assertions = response.getAssertion(); 1091 Iterator iter = assertions.iterator(); 1092 Assertion assertion = null; 1093 String aIDString = null; 1094 String issuer = null; 1095 Iterator stmtIter = null; 1096 Statement statement = null; 1097 int stmtType = Statement.NOT_SUPPORTED; 1098 com.sun.identity.saml.assertion.Subject sub = null; 1099 SubjectConfirmation subConf = null; 1100 Set confMethods = null; 1101 String confMethod = null; 1102 Date date = null; 1103 while (iter.hasNext()) { 1104 assertion = (Assertion) iter.next(); 1105 aIDString = assertion.getAssertionID(); 1106 // make sure it's not being used 1107 if (idTimeMap.containsKey(aIDString)) { 1108 debug.error("verifyAssertion " 1109 + "AndGetSSMap: Assertion: " + aIDString + " is used."); 1110 return null; 1111 } 1112 1113 // check issuer of the assertions 1114 issuer = assertion.getIssuer(); 1115 if ((srcSite = SAMLUtils.getSourceSite(issuer)) == null) { 1116 debug.error("verifyAsserti " 1117 + "onAndGetSSMap: issuer is not on the Partner list."); 1118 return null; 1119 } 1120 1121 if (!assertion.isSignatureValid()) { 1122 debug.error("verifyAssertion " 1123 + "AndGetSSMap: assertion's signature is not valid."); 1124 return null; 1125 } 1126 1127 // must be valid (timewise) 1128 if (!assertion.isTimeValid()) { 1129 debug.error("verifyAssertion " 1130 + "AndGetSSMap: assertion's time is not valid."); 1131 return null; 1132 } 1133 1134 // TODO: IssuerInstant of the assertion is within a few minutes 1135 // This is a MAY in spec. Which number to use for the few minutes? 1136 1137 // TODO: check AudienceRestrictionCondition 1138 1139 //for each assertion, loop to check each statement 1140 stmtIter = assertion.getStatement().iterator(); 1141 while (stmtIter.hasNext()) { 1142 statement = (Statement) stmtIter.next(); 1143 stmtType = statement.getStatementType(); 1144 if ((stmtType == Statement.AUTHENTICATION_STATEMENT) || 1145 (stmtType == Statement.ATTRIBUTE_STATEMENT) || 1146 (stmtType == Statement.AUTHORIZATION_DECISION_STATEMENT)) { 1147 sub = ((SubjectStatement)statement).getSubject(); 1148 1149 // ConfirmationMethod of each subject must be set to bearer 1150 if (((subConf = sub.getSubjectConfirmation()) == null) || 1151 ((confMethods = subConf.getConfirmationMethod()) 1152 == null) || 1153 (confMethods.size() != 1)) { 1154 debug.error("verify " 1155 + "AssertionAndGetSSMap: missing or extra " 1156 + "ConfirmationMethod."); 1157 return null; 1158 } 1159 if (((confMethod = (String) confMethods.iterator().next()) 1160 == null) || 1161 (!confMethod.equals( 1162 SAMLConstants.CONFIRMATION_METHOD_BEARER))) { 1163 debug.error("verify " 1164 + "AssertionAndGetSSMap:wrong ConfirmationMethod."); 1165 return null; 1166 } 1167 1168 //TODO: must contain same Subject for all statements? 1169 1170 if (stmtType == Statement.AUTHENTICATION_STATEMENT) { 1171 //TODO: if it has SubjectLocality,its IP must == sender 1172 // browser IP. This is a MAY item in the spec. 1173 if (subject == null) { 1174 subject = sub; 1175 } 1176 } 1177 } 1178 } 1179 1180 // add the assertion to idTimeMap 1181 if (debug.messageEnabled()) { 1182 debug.message("Adding " + aIDString + " to idTimeMap."); 1183 } 1184 Conditions conds = assertion.getConditions(); 1185 if ((conds != null) && ((date = conds.getNotOnorAfter()) != null)) { 1186 cGoThrough.addElement(aIDString); 1187 idTimeMap.put(aIDString, new Long(date.getTime())); 1188 } else { 1189 cPeriodic.addElement(aIDString); 1190 // it doesn't matter what we store for the value. 1191 idTimeMap.put(aIDString, aIDString); 1192 } 1193 } 1194 1195 // must have at least one SSO assertion 1196 if ((subject == null) || (srcSite == null)) { 1197 debug.error("verifyAssertion AndGetSSMap: couldn't find Subject."); 1198 return null; 1199 } 1200 Map ssMap = new HashMap(); 1201 ssMap.put(SAMLConstants.SUBJECT, subject); 1202 ssMap.put(SAMLConstants.SOURCE_SITE_SOAP_ENTRY, srcSite); 1203 ssMap.put(SAMLConstants.POST_ASSERTION, assertions); 1204 return ssMap; 1205 } 1206 1207 /** 1208 * Checks if the Assertion is time valid and 1209 * if the Assertion is allowed by AudienceRestrictionCondition. 1210 * 1211 * @param assertion an Assertion object 1212 * @return true if the operation is successful otherwise, return false 1213 * @exception IOException IOException 1214 */ 1215 private static boolean checkCondition(Assertion assertion) 1216 throws IOException 1217 { 1218 if (assertion == null) { 1219 return false; 1220 } 1221 if (!assertion.isSignatureValid()) { 1222 debug.error(bundle.getString("assertionSignatureNotValid")); 1223 return false; 1224 } 1225 // check if the Assertion is time valid 1226 if (!(assertion.isTimeValid())) { 1227 debug.error(bundle.getString("assertionTimeNotValid")); 1228 return false; 1229 } 1230 // check the Assertion is allowed by AudienceRestrictionCondition 1231 Conditions cnds = assertion.getConditions(); 1232 Set audienceCnd = new HashSet(); 1233 audienceCnd = cnds.getAudienceRestrictionCondition(); 1234 Iterator it = null; 1235 if (audienceCnd != null) { 1236 if (!audienceCnd.isEmpty()) { 1237 it = audienceCnd.iterator(); 1238 while (it.hasNext()) { 1239 if ((((AudienceRestrictionCondition) it.next()). 1240 evaluate()) == Condition.INDETERMINATE ) { 1241 if (debug.messageEnabled()) { 1242 debug.message("Audience " + 1243 "RestrictionConditions is indeterminate."); 1244 } 1245 } else { 1246 debug.error("Failed AudienceRestrictionCondition"); 1247 return false; 1248 } 1249 } 1250 } 1251 } 1252 return true; 1253 } 1254 1255 /** 1256 * Determines if there is a valid SSO Assertion 1257 * inside of SAML Response. 1258 * 1259 * @param assertions a List of <code>Assertion</code> objects 1260 * @return a Subject object 1261 * @exception IOException IOException 1262 */ 1263 public static com.sun.identity.saml.assertion.Subject examAssertions( 1264 List assertions) throws IOException { 1265 if (assertions == null) { 1266 return null; 1267 } 1268 boolean validation = false; 1269 com.sun.identity.saml.assertion.Subject subject = null; 1270 Iterator iter = assertions.iterator(); 1271 1272 while (iter.hasNext()) { 1273 Assertion assertion = (Assertion)iter.next(); 1274 1275 if (!checkCondition(assertion)) { 1276 return null; 1277 } 1278 debug.message("Passed checking Conditions!"); 1279 1280 // exam the Statement inside the Assertion 1281 Set statements = new HashSet(); 1282 statements = assertion.getStatement(); 1283 1284 if (statements == null || statements.isEmpty()) { 1285 debug.error(bundle.getString("noStatement")); 1286 return null; 1287 } 1288 Iterator iterator = statements.iterator(); 1289 while (iterator.hasNext()) { 1290 Statement statement = (Statement) iterator.next(); 1291 subject = ((SubjectStatement)statement).getSubject(); 1292 SubjectConfirmation sc = subject.getSubjectConfirmation(); 1293 Set cm = new HashSet(); 1294 cm = sc.getConfirmationMethod(); 1295 if (cm == null || cm.isEmpty()) { 1296 debug.error("Subject confirmation method is null"); 1297 return null; 1298 } 1299 String conMethod = (String) cm.iterator().next(); 1300 // add checking artifact confirmation method identifier based 1301 // on Assertion version number 1302 if ((conMethod != null) && (assertion.getMajorVersion() == 1303 SAMLConstants.ASSERTION_MAJOR_VERSION) && 1304 (((assertion.getMinorVersion() == 1305 SAMLConstants.ASSERTION_MINOR_VERSION_ONE) && 1306 conMethod.equals(SAMLConstants.CONFIRMATION_METHOD_ARTIFACT)) 1307 || 1308 ((assertion.getMinorVersion() == 1309 SAMLConstants.ASSERTION_MINOR_VERSION_ZERO) && 1310 (conMethod.equals( 1311 SAMLConstants.DEPRECATED_CONFIRMATION_METHOD_ARTIFACT))))) { 1312 if (debug.messageEnabled()) { 1313 debug.message("Correct Confirmation method"); 1314 } 1315 } else { 1316 debug.error("Wrong Confirmation Method."); 1317 return null; 1318 } 1319 if (statement instanceof AuthenticationStatement) { 1320 //found an SSO Assertion 1321 validation = true; 1322 } 1323 } // end of while (iterator.hasNext()) for Statements 1324 } // end of while (iter.hasNext()) for Assertions 1325 1326 if (!validation) { 1327 debug.error(bundle.getString("noSSOAssertion")); 1328 return null; 1329 } 1330 return subject; 1331 } 1332 1333 /** 1334 * Return whether the signature on the object is valid or not. 1335 * @param xmlString input XML String 1336 * @param idAttribute ASSERTION_ID_ATTRIBUTE or RESPONSE_ID_ATTRIBUTE 1337 * @param issuer the issuer of the Assertion 1338 * @return true if the signature on the object is valid; false otherwise. 1339 */ 1340 public static boolean checkSignatureValid(String xmlString, 1341 String idAttribute, 1342 String issuer) 1343 { 1344 String certAlias = null; 1345 boolean valid = true; 1346 Map entries = (Map) SAMLServiceManager.getAttribute( 1347 SAMLConstants.PARTNER_URLS); 1348 if (entries != null) { 1349 SAMLServiceManager.SOAPEntry srcSite = 1350 (SAMLServiceManager.SOAPEntry) entries.get(issuer); 1351 if (srcSite != null) { 1352 certAlias = srcSite.getCertAlias(); 1353 } 1354 } 1355 1356 try { 1357 XMLSignatureManager manager = XMLSignatureManager.getInstance(); 1358 valid = manager.verifyXMLSignature(xmlString, 1359 idAttribute, certAlias); 1360 } catch (Exception e) { 1361 SAMLUtils.debug.warning("SAMLUtils.checkSignatureValid:"+ 1362 " signature validation exception", e); 1363 valid = false; 1364 } 1365 if (!valid) { 1366 if (SAMLUtils.debug.messageEnabled()) { 1367 SAMLUtils.debug.message("SAMLUtils.checkSignatureValid:"+ 1368 " Couldn't verify signature."); 1369 } 1370 } 1371 return valid; 1372 } 1373 1374 /** 1375 * Sets the given <code>HttpServletResponse</code> object with the 1376 * headers in the given <code>MimeHeaders</code> object. 1377 * @param headers the <code>MimeHeaders</code> object 1378 * @param response the <code>HttpServletResponse</code> object to which the 1379 * headers are to be written. 1380 */ 1381 public static void setMimeHeaders( 1382 MimeHeaders headers, HttpServletResponse response) { 1383 1384 if(headers == null || response == null) { 1385 debug.message("SAMLUtils.setMimeHeaders : null input"); 1386 return; 1387 } 1388 1389 for (Iterator iter = headers.getAllHeaders(); iter.hasNext();){ 1390 MimeHeader header = (MimeHeader)iter.next(); 1391 1392 String[] values = headers.getHeader(header.getName()); 1393 if (values.length == 1) { 1394 response.setHeader(header.getName(), header.getValue()); 1395 } else { 1396 StringBuffer concat = new StringBuffer(); 1397 int i = 0; 1398 while (i < values.length) { 1399 if (i != 0) { 1400 concat.append(','); 1401 } 1402 concat.append(values[i++]); 1403 } 1404 response.setHeader(header.getName(),concat.toString()); 1405 } 1406 1407 } 1408 return; 1409 } 1410 1411 /** 1412 * Returns a <code>MimeHeaders</code> object that contains the headers 1413 * in the given <code>HttpServletRequest</code> object. 1414 * 1415 * @param req the <code>HttpServletRequest</code> object. 1416 * @return a new <code>MimeHeaders</code> object containing the headers. 1417 */ 1418 public static MimeHeaders getMimeHeaders(HttpServletRequest req) { 1419 1420 MimeHeaders headers = new MimeHeaders(); 1421 1422 if(req == null) { 1423 debug.message("SAMLUtils.getMimeHeaders: null input"); 1424 return headers; 1425 } 1426 1427 Enumeration enumerator = req.getHeaderNames(); 1428 1429 while(enumerator.hasMoreElements()) { 1430 String headerName = (String)enumerator.nextElement(); 1431 String headerValue = req.getHeader(headerName); 1432 1433 StringTokenizer values = new StringTokenizer(headerValue, ","); 1434 while(values.hasMoreTokens()) { 1435 headers.addHeader(headerName, values.nextToken().trim()); 1436 } 1437 } 1438 1439 return headers; 1440 } 1441 1442 /** 1443 * Returns the authenticaion login url with goto parameter 1444 * in the given <code>HttpServletRequest</code> object. 1445 * 1446 * @param req the <code>HttpServletRequest</code> object. 1447 * @return a new authenticaion login url with goto parameter. 1448 */ 1449 public static String getLoginRedirectURL(HttpServletRequest req) { 1450 String qs = req.getQueryString(); 1451 String gotoUrl = req.getRequestURL().toString(); 1452 String key = null; 1453 if (qs != null && qs.length() > 0) { 1454 gotoUrl = gotoUrl + "?" + qs; 1455 int startIdx = -1; 1456 int endIdx = -1; 1457 StringBuffer result = new StringBuffer(); 1458 if ((startIdx = qs.indexOf((String) SAMLServiceManager. 1459 getAttribute(SAMLConstants.TARGET_SPECIFIER))) > 0) { 1460 result.append(qs.substring(0, startIdx - 1)); 1461 } 1462 if ((endIdx = qs.indexOf("&", startIdx)) != -1) { 1463 if (startIdx == 0) { 1464 result.append(qs.substring(endIdx + 1)); 1465 } else { 1466 result.append(qs.substring(endIdx)); 1467 } 1468 } 1469 key = result.toString(); 1470 } 1471 1472 String reqUrl = req.getScheme() + "://" + req.getServerName() + ":" + 1473 req.getServerPort() + req.getContextPath(); 1474 String redirectUrl = null; 1475 if (key == null || key.equals("")) { 1476 redirectUrl = reqUrl +"/UI/Login?goto=" + 1477 URLEncDec.encode(gotoUrl); 1478 } else { 1479 redirectUrl = reqUrl +"/UI/Login?" + key + "&goto="+ 1480 URLEncDec.encode(gotoUrl); 1481 } 1482 if (SAMLUtils.debug.messageEnabled()) { 1483 SAMLUtils.debug.message("Redirect to auth login via:" + 1484 redirectUrl); 1485 } 1486 return redirectUrl; 1487 } 1488 1489 1490 /** 1491 * Processes SAML Artifact 1492 * @param artifact SAML Artifact 1493 * @param target Target URL 1494 * @return Attribute Map 1495 * @exception SAMLException if failed to get the Assertions or 1496 * Attribute Map. 1497 */ 1498 public static Map processArtifact(String[] artifact, String target) 1499 throws SAMLException { 1500 List assts = null; 1501 Subject assertionSubject = null; 1502 AssertionArtifact firstArtifact = null; 1503 Map sessMap = null; 1504 // Call SAMLClient to do the Single-sign-on 1505 try { 1506 assts = SAMLClient.artifactQueryHandler(artifact, (String) null); 1507 //exam the SAML response 1508 if ((assertionSubject = examAssertions(assts)) == null) { 1509 return null; 1510 } 1511 firstArtifact = new AssertionArtifact(artifact[0]); 1512 String sid = firstArtifact.getSourceID(); 1513 Map partner = (Map) SAMLServiceManager.getAttribute( 1514 SAMLConstants.PARTNER_URLS); 1515 if (partner == null) { 1516 throw new SAMLException(bundle.getString 1517 ("nullPartnerUrl")); 1518 } 1519 SAMLServiceManager.SOAPEntry partnerdest = 1520 (SAMLServiceManager.SOAPEntry) partner.get(sid); 1521 if (partnerdest == null) { 1522 throw new SAMLException(bundle.getString 1523 ("failedAccountMapping")); 1524 } 1525 sessMap = getAttributeMap(partnerdest, assts, 1526 assertionSubject, target); 1527 } catch (Exception se) { 1528 debug.error("SAMLUtils.processArtifact :" , se); 1529 throw new SAMLException( 1530 bundle.getString("failProcessArtifact")); 1531 } 1532 return sessMap; 1533 } 1534 1535 /** 1536 * Creates Session 1537 * @param request HttpServletRequest 1538 * @param response HttpServletResponse 1539 * @param attrMap Attribute Map 1540 * @exception if failed to create Session 1541 */ 1542 public static Object generateSession(HttpServletRequest request, 1543 HttpServletResponse response, 1544 Map attrMap) throws SAMLException { 1545 Map sessionInfoMap = new HashMap(); 1546 String realm = (String) attrMap.get(SessionProvider.REALM); 1547 if ((realm == null) || (realm.length() == 0)) { 1548 realm = "/"; 1549 } 1550 sessionInfoMap.put(SessionProvider.REALM, realm); 1551 String principalName = 1552 (String) attrMap.get(SessionProvider.PRINCIPAL_NAME); 1553 if (principalName == null) { 1554 principalName = (String) attrMap.get(SAMLConstants.USER_NAME); 1555 } 1556 sessionInfoMap.put(SessionProvider.PRINCIPAL_NAME, principalName); 1557 //TODO: sessionInfoMap.put(SessionProvider.AUTH_LEVEL, "0"); 1558 Object session = null; 1559 try { 1560 SessionProvider sessionProvider = SessionManager.getProvider(); 1561 session = sessionProvider.createSession( 1562 sessionInfoMap, request, response, null); 1563 setAttrMapInSession(sessionProvider, attrMap, session); 1564 } catch (SessionException se) { 1565 if (debug.messageEnabled()) { 1566 debug.message("SAMLUtils.generateSession:", se); 1567 } 1568 throw new SAMLException(se); 1569 } 1570 return session; 1571 } 1572 1573 /** 1574 * Processes SAML Response 1575 * @param samlResponse SAML Response object 1576 * @param target Target URL 1577 * @return Attribute Map 1578 * @exception SAMLException if failed to get Attribute Map. 1579 */ 1580 public static Map processResponse(Response samlResponse, String target) 1581 throws SAMLException { 1582 List assertions = null; 1583 SAMLServiceManager.SOAPEntry partnerdest = null; 1584 Subject assertionSubject = null; 1585 if (samlResponse.isSigned()) { 1586 // verify the signature 1587 boolean isSignedandValid = verifySignature(samlResponse); 1588 if (!isSignedandValid) { 1589 throw new SAMLException(bundle.getString("invalidResponse")); 1590 } 1591 } 1592 // check Assertion and get back a Map of relevant data including, 1593 // Subject, SOAPEntry for the partner and the List of Assertions. 1594 Map ssMap = verifyAssertionAndGetSSMap(samlResponse); 1595 if (debug.messageEnabled()) { 1596 debug.message("processResponse: ssMap = " + ssMap); 1597 } 1598 1599 if (ssMap == null) { 1600 throw new SAMLException(bundle.getString("invalidAssertion")); 1601 } 1602 assertionSubject = (com.sun.identity.saml.assertion.Subject) 1603 ssMap.get(SAMLConstants.SUBJECT); 1604 if (assertionSubject == null) { 1605 throw new SAMLException(bundle.getString("nullSubject")); 1606 } 1607 1608 partnerdest = (SAMLServiceManager.SOAPEntry)ssMap 1609 .get(SAMLConstants.SOURCE_SITE_SOAP_ENTRY); 1610 if (partnerdest == null) { 1611 throw new SAMLException(bundle.getString("failedAccountMapping")); 1612 } 1613 1614 assertions = (List)ssMap.get(SAMLConstants.POST_ASSERTION); 1615 Map sessMap = null; 1616 try { 1617 sessMap = getAttributeMap(partnerdest, assertions, 1618 assertionSubject, target); 1619 } catch (Exception se) { 1620 debug.error("SAMLUtils.processResponse :" , se); 1621 throw new SAMLException( 1622 bundle.getString("failProcessResponse")); 1623 } 1624 return sessMap; 1625 } 1626 1627 /** 1628 *Sets the attribute map in the session 1629 * 1630 *@param attrMap, the Attribute Map 1631 *@param session, the valid session object 1632 *@exception SessionException if failed to set Attribute in the 1633 * Session. 1634 */ 1635 private static void setAttrMapInSession( 1636 SessionProvider sessionProvider, 1637 Map attrMap, Object session) 1638 throws SessionException { 1639 if (attrMap != null && !attrMap.isEmpty()) { 1640 Set entrySet = attrMap.entrySet(); 1641 for(Iterator iter = entrySet.iterator(); iter.hasNext();) { 1642 Map.Entry entry = (Map.Entry)iter.next(); 1643 String attrName = (String)entry.getKey(); 1644 String[] attrValues = null; 1645 if (attrName.equals(SAMLConstants.USER_NAME) || 1646 attrName.equals(SessionProvider.PRINCIPAL_NAME)) { 1647 String attrValue = (String)entry.getValue(); 1648 attrValues = new String[1]; 1649 attrValues[0] = attrValue; 1650 } else if (attrName.equals(SessionProvider.REALM) || 1651 attrName.equals(SessionProvider.AUTH_LEVEL)) { 1652 // ignore 1653 continue; 1654 } else { 1655 attrValues = (String[])entry.getValue(); 1656 } 1657 sessionProvider.setProperty(session, attrName, attrValues); 1658 if (debug.messageEnabled()) { 1659 debug.message("SAMLUtils.setAttrMapInSessioin: attrName ="+ 1660 attrName); 1661 } 1662 } 1663 } 1664 } 1665 1666 /** 1667 * Compares two URLs to see if they are equal. Two URLs are equal if 1668 * they have same protocol, host, port and path (case ignored). 1669 * Note : the method is provided to avoid URL.equals() call which requires 1670 * name lookup. Name lookup is a blocking operation and very expensive 1671 * if the hostname could not be resolved. 1672 * 1673 * @return true if the URLs are equal, false otherwise. 1674 */ 1675 private static boolean equalURL(String url1, String url2) { 1676 try { 1677 URL u1 = new URL(url1); 1678 URL u2 = new URL(url2); 1679 int port1 = u1.getPort(); 1680 if (port1 == -1) { 1681 port1 = u1.getDefaultPort(); 1682 } 1683 int port2 = u2.getPort(); 1684 if (port2 == -1) { 1685 port2 = u2.getDefaultPort(); 1686 } 1687 if ((u1.getProtocol().equalsIgnoreCase(u2.getProtocol())) && 1688 (u1.getHost().equalsIgnoreCase(u2.getHost())) && 1689 (port1 == port2) && 1690 (u1.getPath().equalsIgnoreCase(u2.getPath()))) { 1691 return true; 1692 } else { 1693 return false; 1694 } 1695 } catch (MalformedURLException m) { 1696 debug.message("Error in SAMLUtils.equalURL", m); 1697 return false; 1698 } 1699 } 1700 1701 /** 1702 * Gets input Node Canonicalized 1703 * 1704 * @param node Node 1705 * @return Canonical element if the operation succeeded. 1706 * Otherwise, return null. 1707 */ 1708 public static Element getCanonicalElement(Node node) { 1709 try { 1710 Canonicalizer c14n = Canonicalizer.getInstance( 1711 "http://www.w3.org/TR/2001/REC-xml-c14n-20010315"); 1712 byte outputBytes[] = c14n.canonicalizeSubtree(node); 1713 DocumentBuilder documentBuilder = 1714 XMLUtils.getSafeDocumentBuilder(false); 1715 Document doc = documentBuilder.parse( 1716 new ByteArrayInputStream(outputBytes)); 1717 Element result = doc.getDocumentElement(); 1718 return result; 1719 } catch (Exception e) { 1720 SAMLUtils.debug.error("Response:getCanonicalElement: " + 1721 "Error while performing canonicalization on " + 1722 "the input Node."); 1723 return null; 1724 } 1725 } 1726 1727 /** 1728 * Sends to error page URL for SAML protocols. If the error page is 1729 * hosted in the same web application, forward is used with 1730 * parameters. Otherwise, redirection or HTTP POST is used with 1731 * parameters. 1732 * Three parameters are passed to the error URL: 1733 * -- errorcode : Error key, this is the I18n key of the error message. 1734 * -- httpstatuscode : Http status code for the error 1735 * -- message : detailed I18n'd error message 1736 * @param request HttpServletRequest object 1737 * @param response HttpServletResponse object 1738 * @param httpStatusCode Http Status code 1739 * @param errorCode Error code 1740 * @param errorMsg Detailed error message 1741 */ 1742 public static void sendError(HttpServletRequest request, 1743 HttpServletResponse response, int httpStatusCode, 1744 String errorCode, String errorMsg) { 1745 String errorUrl = SystemConfigurationUtil.getProperty( 1746 SAMLConstants.ERROR_PAGE_URL, 1747 SAMLConstants.DEFAULT_ERROR_PAGE_URL); 1748 if(debug.messageEnabled()) { 1749 debug.message("SAMLUtils.sendError: error page" + errorUrl); 1750 } 1751 String tmp = errorUrl.toLowerCase(); 1752 if (!tmp.startsWith("http://") && !tmp.startsWith("https://")) { 1753 // use forward 1754 String jointString = "?"; 1755 if (errorUrl.indexOf("?") != -1) { 1756 jointString = "&"; 1757 } 1758 String newUrl = errorUrl.trim() + jointString 1759 + SAMLConstants.ERROR_CODE + "=" + errorCode + "&" 1760 + SAMLConstants.HTTP_STATUS_CODE + "=" + httpStatusCode 1761 + "&" + SAMLConstants.ERROR_MESSAGE + "=" 1762 + URLEncDec.encode(errorMsg); 1763 1764 forwardRequest(newUrl, request, response); 1765 } else { 1766 String binding = SystemConfigurationUtil.getProperty( 1767 SAMLConstants.ERROR_PAGE_HTTP_BINDING, 1768 SAMLConstants.HTTP_POST); 1769 if(SAMLConstants.HTTP_REDIRECT.equals(binding)) { 1770 // use FSUtils, this may be redirection or forward 1771 String jointString = "?"; 1772 if (errorUrl.indexOf("?") != -1) { 1773 jointString = "&"; 1774 } 1775 String newUrl = errorUrl.trim() + jointString 1776 + SAMLConstants.ERROR_CODE + "=" + errorCode + "&" 1777 + SAMLConstants.HTTP_STATUS_CODE + "=" + httpStatusCode 1778 + "&" + SAMLConstants.ERROR_MESSAGE + "=" 1779 + URLEncDec.encode(errorMsg); 1780 1781 FSUtils.forwardRequest(request, response, newUrl) ; 1782 } else { 1783 // Populate request attributes to be available for rendering. 1784 request.setAttribute("ERROR_URL", errorUrl); 1785 request.setAttribute("ERROR_CODE_NAME", SAMLConstants.ERROR_CODE); 1786 request.setAttribute("ERROR_CODE", errorCode); 1787 request.setAttribute("ERROR_MESSAGE_NAME", SAMLConstants.ERROR_MESSAGE); 1788 request.setAttribute("ERROR_MESSAGE", URLEncDec.encode(errorMsg)); 1789 request.setAttribute("HTTP_STATUS_CODE_NAME", SAMLConstants.HTTP_STATUS_CODE); 1790 request.setAttribute("HTTP_STATUS_CODE", httpStatusCode); 1791 request.setAttribute("SAML_ERROR_KEY", bundle.getString("samlErrorKey")); 1792 // Forward to auto-submitting form. 1793 forwardRequest(ERROR_JSP, request, response); 1794 } 1795 } 1796 } 1797 1798 /** 1799 * Forwards to the passed URL. 1800 * 1801 * @param url 1802 * Forward URL 1803 * @param request 1804 * Request object 1805 * @param response 1806 * Response object 1807 */ 1808 private static void forwardRequest(String url, HttpServletRequest request, HttpServletResponse response) { 1809 try { 1810 request.getRequestDispatcher(url).forward(request, response); 1811 1812 } catch (ServletException sE) { 1813 handleForwardError(url, sE, response); 1814 } catch (IOException ioE) { 1815 handleForwardError(url, ioE, response); 1816 } 1817 } 1818 1819 /** 1820 * Handle any forward error. 1821 * 1822 * @param url 1823 * Attempted forward URL 1824 * @param exception 1825 * Caught exception 1826 * @param response 1827 * Response object 1828 */ 1829 private static void handleForwardError(String url, Exception exception, HttpServletResponse response) { 1830 debug.error("SAMLUtils.sendError: Exception occurred while trying to forward to resource: " + url, exception); 1831 1832 try { 1833 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, exception.getMessage()); 1834 } catch (IOException ioE) { 1835 debug.error("Failed to inform the response of caught exception", ioE); 1836 } 1837 } 1838 1839}
Copyright © 2010-2017, ForgeRock All Rights Reserved.