001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.hadoop.hdfs.server.common; 020 021import static org.apache.hadoop.fs.CommonConfigurationKeys.DEFAULT_HADOOP_HTTP_STATIC_USER; 022import static org.apache.hadoop.fs.CommonConfigurationKeys.HADOOP_HTTP_STATIC_USER; 023 024import java.io.ByteArrayInputStream; 025import java.io.DataInputStream; 026import java.io.IOException; 027import java.io.UnsupportedEncodingException; 028import java.net.InetAddress; 029import java.net.InetSocketAddress; 030import java.net.Socket; 031import java.net.URL; 032import java.net.URLEncoder; 033import java.util.Arrays; 034import java.util.Collections; 035import java.util.Comparator; 036import java.util.HashMap; 037import java.util.List; 038 039import javax.servlet.ServletContext; 040import javax.servlet.http.HttpServletRequest; 041import javax.servlet.jsp.JspWriter; 042 043import org.apache.commons.logging.Log; 044import org.apache.commons.logging.LogFactory; 045import org.apache.hadoop.classification.InterfaceAudience; 046import org.apache.hadoop.conf.Configuration; 047import org.apache.hadoop.fs.Path; 048import org.apache.hadoop.hdfs.BlockReader; 049import org.apache.hadoop.hdfs.BlockReaderFactory; 050import org.apache.hadoop.hdfs.ClientContext; 051import org.apache.hadoop.hdfs.DFSClient; 052import org.apache.hadoop.hdfs.DFSUtil; 053import org.apache.hadoop.hdfs.RemotePeerFactory; 054import org.apache.hadoop.hdfs.net.Peer; 055import org.apache.hadoop.hdfs.net.TcpPeerServer; 056import org.apache.hadoop.hdfs.protocol.DatanodeID; 057import org.apache.hadoop.hdfs.protocol.DatanodeInfo; 058import org.apache.hadoop.hdfs.protocol.ExtendedBlock; 059import org.apache.hadoop.hdfs.protocol.LocatedBlock; 060import org.apache.hadoop.hdfs.protocol.LocatedBlocks; 061import org.apache.hadoop.hdfs.security.token.block.BlockTokenIdentifier; 062import org.apache.hadoop.hdfs.security.token.block.DataEncryptionKey; 063import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier; 064import org.apache.hadoop.hdfs.server.blockmanagement.DatanodeDescriptor; 065import org.apache.hadoop.hdfs.server.datanode.CachingStrategy; 066import org.apache.hadoop.hdfs.server.namenode.NameNode; 067import org.apache.hadoop.hdfs.server.namenode.NameNodeHttpServer; 068import org.apache.hadoop.hdfs.web.resources.DelegationParam; 069import org.apache.hadoop.hdfs.web.resources.DoAsParam; 070import org.apache.hadoop.hdfs.web.resources.UserParam; 071import org.apache.hadoop.http.HtmlQuoting; 072import org.apache.hadoop.io.IOUtils; 073import org.apache.hadoop.net.NetUtils; 074import org.apache.hadoop.security.AccessControlException; 075import org.apache.hadoop.security.SecurityUtil; 076import org.apache.hadoop.security.UserGroupInformation; 077import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod; 078import org.apache.hadoop.security.authentication.util.KerberosName; 079import org.apache.hadoop.security.authorize.ProxyServers; 080import org.apache.hadoop.security.authorize.ProxyUsers; 081import org.apache.hadoop.security.token.Token; 082import org.apache.hadoop.util.VersionInfo; 083 084import com.google.common.base.Charsets; 085 086@InterfaceAudience.Private 087public class JspHelper { 088 public static final String CURRENT_CONF = "current.conf"; 089 public static final String DELEGATION_PARAMETER_NAME = DelegationParam.NAME; 090 public static final String NAMENODE_ADDRESS = "nnaddr"; 091 static final String SET_DELEGATION = "&" + DELEGATION_PARAMETER_NAME + 092 "="; 093 private static final Log LOG = LogFactory.getLog(JspHelper.class); 094 095 /** Private constructor for preventing creating JspHelper object. */ 096 private JspHelper() {} 097 098 // data structure to count number of blocks on datanodes. 099 private static class NodeRecord extends DatanodeInfo { 100 int frequency; 101 102 public NodeRecord(DatanodeInfo info, int count) { 103 super(info); 104 this.frequency = count; 105 } 106 107 @Override 108 public boolean equals(Object obj) { 109 // Sufficient to use super equality as datanodes are uniquely identified 110 // by DatanodeID 111 return (this == obj) || super.equals(obj); 112 } 113 @Override 114 public int hashCode() { 115 // Super implementation is sufficient 116 return super.hashCode(); 117 } 118 } 119 120 // compare two records based on their frequency 121 private static class NodeRecordComparator implements Comparator<NodeRecord> { 122 123 @Override 124 public int compare(NodeRecord o1, NodeRecord o2) { 125 if (o1.frequency < o2.frequency) { 126 return -1; 127 } else if (o1.frequency > o2.frequency) { 128 return 1; 129 } 130 return 0; 131 } 132 } 133 134 /** 135 * convenience method for canonicalizing host name. 136 * @param addr name:port or name 137 * @return canonicalized host name 138 */ 139 public static String canonicalize(String addr) { 140 // default port 1 is supplied to allow addr without port. 141 // the port will be ignored. 142 return NetUtils.createSocketAddr(addr, 1).getAddress() 143 .getCanonicalHostName(); 144 } 145 146 /** 147 * A helper class that generates the correct URL for different schema. 148 * 149 */ 150 public static final class Url { 151 public static String authority(String scheme, DatanodeID d) { 152 String fqdn = (d.getIpAddr() != null && !d.getIpAddr().isEmpty())? 153 canonicalize(d.getIpAddr()): 154 d.getHostName(); 155 if (scheme.equals("http")) { 156 return fqdn + ":" + d.getInfoPort(); 157 } else if (scheme.equals("https")) { 158 return fqdn + ":" + d.getInfoSecurePort(); 159 } else { 160 throw new IllegalArgumentException("Unknown scheme:" + scheme); 161 } 162 } 163 164 public static String url(String scheme, DatanodeID d) { 165 return scheme + "://" + authority(scheme, d); 166 } 167 } 168 169 public static DatanodeInfo bestNode(LocatedBlocks blks, Configuration conf) 170 throws IOException { 171 HashMap<DatanodeInfo, NodeRecord> map = 172 new HashMap<DatanodeInfo, NodeRecord>(); 173 for (LocatedBlock block : blks.getLocatedBlocks()) { 174 DatanodeInfo[] nodes = block.getLocations(); 175 for (DatanodeInfo node : nodes) { 176 NodeRecord record = map.get(node); 177 if (record == null) { 178 map.put(node, new NodeRecord(node, 1)); 179 } else { 180 record.frequency++; 181 } 182 } 183 } 184 NodeRecord[] nodes = map.values().toArray(new NodeRecord[map.size()]); 185 Arrays.sort(nodes, new NodeRecordComparator()); 186 return bestNode(nodes, false); 187 } 188 189 public static DatanodeInfo bestNode(LocatedBlock blk, Configuration conf) 190 throws IOException { 191 DatanodeInfo[] nodes = blk.getLocations(); 192 return bestNode(nodes, true); 193 } 194 195 private static DatanodeInfo bestNode(DatanodeInfo[] nodes, boolean doRandom) 196 throws IOException { 197 if (nodes == null || nodes.length == 0) { 198 throw new IOException("No nodes contain this block"); 199 } 200 int l = 0; 201 while (l < nodes.length && !nodes[l].isDecommissioned()) { 202 ++l; 203 } 204 205 if (l == 0) { 206 throw new IOException("No active nodes contain this block"); 207 } 208 209 int index = doRandom ? DFSUtil.getRandom().nextInt(l) : 0; 210 return nodes[index]; 211 } 212 213 public static void streamBlockInAscii(InetSocketAddress addr, String poolId, 214 long blockId, Token<BlockTokenIdentifier> blockToken, long genStamp, 215 long blockSize, long offsetIntoBlock, long chunkSizeToView, 216 JspWriter out, final Configuration conf, DFSClient.Conf dfsConf, 217 final DataEncryptionKey encryptionKey) 218 throws IOException { 219 if (chunkSizeToView == 0) return; 220 int amtToRead = (int)Math.min(chunkSizeToView, blockSize - offsetIntoBlock); 221 222 BlockReader blockReader = new BlockReaderFactory(dfsConf). 223 setInetSocketAddress(addr). 224 setBlock(new ExtendedBlock(poolId, blockId, 0, genStamp)). 225 setFileName(BlockReaderFactory.getFileName(addr, poolId, blockId)). 226 setBlockToken(blockToken). 227 setStartOffset(offsetIntoBlock). 228 setLength(amtToRead). 229 setVerifyChecksum(true). 230 setClientName("JspHelper"). 231 setClientCacheContext(ClientContext.getFromConf(conf)). 232 setDatanodeInfo(new DatanodeInfo( 233 new DatanodeID(addr.getAddress().getHostAddress(), 234 addr.getHostName(), poolId, addr.getPort(), 0, 0, 0))). 235 setCachingStrategy(CachingStrategy.newDefaultStrategy()). 236 setConfiguration(conf). 237 setRemotePeerFactory(new RemotePeerFactory() { 238 @Override 239 public Peer newConnectedPeer(InetSocketAddress addr) 240 throws IOException { 241 Peer peer = null; 242 Socket sock = NetUtils.getDefaultSocketFactory(conf).createSocket(); 243 try { 244 sock.connect(addr, HdfsServerConstants.READ_TIMEOUT); 245 sock.setSoTimeout(HdfsServerConstants.READ_TIMEOUT); 246 peer = TcpPeerServer.peerFromSocketAndKey(sock, encryptionKey); 247 } finally { 248 if (peer == null) { 249 IOUtils.closeSocket(sock); 250 } 251 } 252 return peer; 253 } 254 }). 255 build(); 256 257 final byte[] buf = new byte[amtToRead]; 258 try { 259 int readOffset = 0; 260 int retries = 2; 261 while (amtToRead > 0) { 262 int numRead = amtToRead; 263 try { 264 blockReader.readFully(buf, readOffset, amtToRead); 265 } catch (IOException e) { 266 retries--; 267 if (retries == 0) 268 throw new IOException("Could not read data from datanode"); 269 continue; 270 } 271 amtToRead -= numRead; 272 readOffset += numRead; 273 } 274 } finally { 275 blockReader.close(); 276 } 277 out.print(HtmlQuoting.quoteHtmlChars(new String(buf, Charsets.UTF_8))); 278 } 279 280 public static void addTableHeader(JspWriter out) throws IOException { 281 out.print("<table border=\"1\""+ 282 " cellpadding=\"2\" cellspacing=\"2\">"); 283 out.print("<tbody>"); 284 } 285 public static void addTableRow(JspWriter out, String[] columns) throws IOException { 286 out.print("<tr>"); 287 for (int i = 0; i < columns.length; i++) { 288 out.print("<td style=\"vertical-align: top;\"><B>"+columns[i]+"</B><br></td>"); 289 } 290 out.print("</tr>"); 291 } 292 public static void addTableRow(JspWriter out, String[] columns, int row) throws IOException { 293 out.print("<tr>"); 294 295 for (int i = 0; i < columns.length; i++) { 296 if (row/2*2 == row) {//even 297 out.print("<td style=\"vertical-align: top;background-color:LightGrey;\"><B>"+columns[i]+"</B><br></td>"); 298 } else { 299 out.print("<td style=\"vertical-align: top;background-color:LightBlue;\"><B>"+columns[i]+"</B><br></td>"); 300 301 } 302 } 303 out.print("</tr>"); 304 } 305 public static void addTableFooter(JspWriter out) throws IOException { 306 out.print("</tbody></table>"); 307 } 308 309 public static void sortNodeList(final List<DatanodeDescriptor> nodes, 310 String field, String order) { 311 312 class NodeComapare implements Comparator<DatanodeDescriptor> { 313 static final int 314 FIELD_NAME = 1, 315 FIELD_LAST_CONTACT = 2, 316 FIELD_BLOCKS = 3, 317 FIELD_CAPACITY = 4, 318 FIELD_USED = 5, 319 FIELD_PERCENT_USED = 6, 320 FIELD_NONDFS_USED = 7, 321 FIELD_REMAINING = 8, 322 FIELD_PERCENT_REMAINING = 9, 323 FIELD_ADMIN_STATE = 10, 324 FIELD_DECOMMISSIONED = 11, 325 SORT_ORDER_ASC = 1, 326 SORT_ORDER_DSC = 2; 327 328 int sortField = FIELD_NAME; 329 int sortOrder = SORT_ORDER_ASC; 330 331 public NodeComapare(String field, String order) { 332 if (field.equals("lastcontact")) { 333 sortField = FIELD_LAST_CONTACT; 334 } else if (field.equals("capacity")) { 335 sortField = FIELD_CAPACITY; 336 } else if (field.equals("used")) { 337 sortField = FIELD_USED; 338 } else if (field.equals("nondfsused")) { 339 sortField = FIELD_NONDFS_USED; 340 } else if (field.equals("remaining")) { 341 sortField = FIELD_REMAINING; 342 } else if (field.equals("pcused")) { 343 sortField = FIELD_PERCENT_USED; 344 } else if (field.equals("pcremaining")) { 345 sortField = FIELD_PERCENT_REMAINING; 346 } else if (field.equals("blocks")) { 347 sortField = FIELD_BLOCKS; 348 } else if (field.equals("adminstate")) { 349 sortField = FIELD_ADMIN_STATE; 350 } else if (field.equals("decommissioned")) { 351 sortField = FIELD_DECOMMISSIONED; 352 } else { 353 sortField = FIELD_NAME; 354 } 355 356 if (order.equals("DSC")) { 357 sortOrder = SORT_ORDER_DSC; 358 } else { 359 sortOrder = SORT_ORDER_ASC; 360 } 361 } 362 363 @Override 364 public int compare(DatanodeDescriptor d1, 365 DatanodeDescriptor d2) { 366 int ret = 0; 367 switch (sortField) { 368 case FIELD_LAST_CONTACT: 369 ret = (int) (d2.getLastUpdate() - d1.getLastUpdate()); 370 break; 371 case FIELD_CAPACITY: 372 long dlong = d1.getCapacity() - d2.getCapacity(); 373 ret = (dlong < 0) ? -1 : ((dlong > 0) ? 1 : 0); 374 break; 375 case FIELD_USED: 376 dlong = d1.getDfsUsed() - d2.getDfsUsed(); 377 ret = (dlong < 0) ? -1 : ((dlong > 0) ? 1 : 0); 378 break; 379 case FIELD_NONDFS_USED: 380 dlong = d1.getNonDfsUsed() - d2.getNonDfsUsed(); 381 ret = (dlong < 0) ? -1 : ((dlong > 0) ? 1 : 0); 382 break; 383 case FIELD_REMAINING: 384 dlong = d1.getRemaining() - d2.getRemaining(); 385 ret = (dlong < 0) ? -1 : ((dlong > 0) ? 1 : 0); 386 break; 387 case FIELD_PERCENT_USED: 388 double ddbl =((d1.getDfsUsedPercent())- 389 (d2.getDfsUsedPercent())); 390 ret = (ddbl < 0) ? -1 : ((ddbl > 0) ? 1 : 0); 391 break; 392 case FIELD_PERCENT_REMAINING: 393 ddbl =((d1.getRemainingPercent())- 394 (d2.getRemainingPercent())); 395 ret = (ddbl < 0) ? -1 : ((ddbl > 0) ? 1 : 0); 396 break; 397 case FIELD_BLOCKS: 398 ret = d1.numBlocks() - d2.numBlocks(); 399 break; 400 case FIELD_ADMIN_STATE: 401 ret = d1.getAdminState().toString().compareTo( 402 d2.getAdminState().toString()); 403 break; 404 case FIELD_DECOMMISSIONED: 405 ret = DFSUtil.DECOM_COMPARATOR.compare(d1, d2); 406 break; 407 case FIELD_NAME: 408 ret = d1.getHostName().compareTo(d2.getHostName()); 409 break; 410 default: 411 throw new IllegalArgumentException("Invalid sortField"); 412 } 413 return (sortOrder == SORT_ORDER_DSC) ? -ret : ret; 414 } 415 } 416 417 Collections.sort(nodes, new NodeComapare(field, order)); 418 } 419 420 public static void printPathWithLinks(String dir, JspWriter out, 421 int namenodeInfoPort, 422 String tokenString, 423 String nnAddress 424 ) throws IOException { 425 try { 426 String[] parts = dir.split(Path.SEPARATOR); 427 StringBuilder tempPath = new StringBuilder(dir.length()); 428 out.print("<a href=\"browseDirectory.jsp" + "?dir="+ Path.SEPARATOR 429 + "&namenodeInfoPort=" + namenodeInfoPort 430 + getDelegationTokenUrlParam(tokenString) 431 + getUrlParam(NAMENODE_ADDRESS, nnAddress) + "\">" + Path.SEPARATOR 432 + "</a>"); 433 tempPath.append(Path.SEPARATOR); 434 for (int i = 0; i < parts.length-1; i++) { 435 if (!parts[i].equals("")) { 436 tempPath.append(parts[i]); 437 out.print("<a href=\"browseDirectory.jsp" + "?dir=" 438 + HtmlQuoting.quoteHtmlChars(tempPath.toString()) + "&namenodeInfoPort=" + namenodeInfoPort 439 + getDelegationTokenUrlParam(tokenString) 440 + getUrlParam(NAMENODE_ADDRESS, nnAddress)); 441 out.print("\">" + HtmlQuoting.quoteHtmlChars(parts[i]) + "</a>" + Path.SEPARATOR); 442 tempPath.append(Path.SEPARATOR); 443 } 444 } 445 if(parts.length > 0) { 446 out.print(HtmlQuoting.quoteHtmlChars(parts[parts.length-1])); 447 } 448 } 449 catch (UnsupportedEncodingException ex) { 450 ex.printStackTrace(); 451 } 452 } 453 454 public static void printGotoForm(JspWriter out, 455 int namenodeInfoPort, 456 String tokenString, 457 String file, 458 String nnAddress) throws IOException { 459 out.print("<form action=\"browseDirectory.jsp\" method=\"get\" name=\"goto\">"); 460 out.print("Goto : "); 461 out.print("<input name=\"dir\" type=\"text\" width=\"50\" id=\"dir\" value=\""+ HtmlQuoting.quoteHtmlChars(file)+"\"/>"); 462 out.print("<input name=\"go\" type=\"submit\" value=\"go\"/>"); 463 out.print("<input name=\"namenodeInfoPort\" type=\"hidden\" " 464 + "value=\"" + namenodeInfoPort + "\"/>"); 465 if (UserGroupInformation.isSecurityEnabled()) { 466 out.print("<input name=\"" + DELEGATION_PARAMETER_NAME 467 + "\" type=\"hidden\" value=\"" + tokenString + "\"/>"); 468 } 469 out.print("<input name=\""+ NAMENODE_ADDRESS +"\" type=\"hidden\" " 470 + "value=\"" + nnAddress + "\"/>"); 471 out.print("</form>"); 472 } 473 474 public static void createTitle(JspWriter out, 475 HttpServletRequest req, 476 String file) throws IOException{ 477 if(file == null) file = ""; 478 int start = Math.max(0,file.length() - 100); 479 if(start != 0) 480 file = "..." + file.substring(start, file.length()); 481 out.print("<title>HDFS:" + file + "</title>"); 482 } 483 484 /** Convert a String to chunk-size-to-view. */ 485 public static int string2ChunkSizeToView(String s, int defaultValue) { 486 int n = s == null? 0: Integer.parseInt(s); 487 return n > 0? n: defaultValue; 488 } 489 490 /** Return a table containing version information. */ 491 public static String getVersionTable() { 492 return "<div class='dfstable'><table>" 493 + "\n <tr><td class='col1'>Version:</td><td>" + VersionInfo.getVersion() + ", " + VersionInfo.getRevision() + "</td></tr>" 494 + "\n <tr><td class='col1'>Compiled:</td><td>" + VersionInfo.getDate() + " by " + VersionInfo.getUser() + " from " + VersionInfo.getBranch() + "</td></tr>" 495 + "\n</table></div>"; 496 } 497 498 /** 499 * Validate filename. 500 * @return null if the filename is invalid. 501 * Otherwise, return the validated filename. 502 */ 503 public static String validatePath(String p) { 504 return p == null || p.length() == 0? 505 null: new Path(p).toUri().getPath(); 506 } 507 508 /** 509 * Validate a long value. 510 * @return null if the value is invalid. 511 * Otherwise, return the validated Long object. 512 */ 513 public static Long validateLong(String value) { 514 return value == null? null: Long.parseLong(value); 515 } 516 517 /** 518 * Validate a URL. 519 * @return null if the value is invalid. 520 * Otherwise, return the validated URL String. 521 */ 522 public static String validateURL(String value) { 523 try { 524 return URLEncoder.encode(new URL(value).toString(), "UTF-8"); 525 } catch (IOException e) { 526 return null; 527 } 528 } 529 530 /** 531 * If security is turned off, what is the default web user? 532 * @param conf the configuration to look in 533 * @return the remote user that was configuration 534 */ 535 public static UserGroupInformation getDefaultWebUser(Configuration conf 536 ) throws IOException { 537 return UserGroupInformation.createRemoteUser(getDefaultWebUserName(conf)); 538 } 539 540 private static String getDefaultWebUserName(Configuration conf 541 ) throws IOException { 542 String user = conf.get( 543 HADOOP_HTTP_STATIC_USER, DEFAULT_HADOOP_HTTP_STATIC_USER); 544 if (user == null || user.length() == 0) { 545 throw new IOException("Cannot determine UGI from request or conf"); 546 } 547 return user; 548 } 549 550 private static InetSocketAddress getNNServiceAddress(ServletContext context, 551 HttpServletRequest request) { 552 String namenodeAddressInUrl = request.getParameter(NAMENODE_ADDRESS); 553 InetSocketAddress namenodeAddress = null; 554 if (namenodeAddressInUrl != null) { 555 namenodeAddress = NetUtils.createSocketAddr(namenodeAddressInUrl); 556 } else if (context != null) { 557 namenodeAddress = NameNodeHttpServer.getNameNodeAddressFromContext( 558 context); 559 } 560 if (namenodeAddress != null) { 561 return namenodeAddress; 562 } 563 return null; 564 } 565 566 /** Same as getUGI(null, request, conf). */ 567 public static UserGroupInformation getUGI(HttpServletRequest request, 568 Configuration conf) throws IOException { 569 return getUGI(null, request, conf); 570 } 571 572 /** Same as getUGI(context, request, conf, KERBEROS_SSL, true). */ 573 public static UserGroupInformation getUGI(ServletContext context, 574 HttpServletRequest request, Configuration conf) throws IOException { 575 return getUGI(context, request, conf, AuthenticationMethod.KERBEROS_SSL, true); 576 } 577 578 /** 579 * Get {@link UserGroupInformation} and possibly the delegation token out of 580 * the request. 581 * @param context the ServletContext that is serving this request. 582 * @param request the http request 583 * @param conf configuration 584 * @param secureAuthMethod the AuthenticationMethod used in secure mode. 585 * @param tryUgiParameter Should it try the ugi parameter? 586 * @return a new user from the request 587 * @throws AccessControlException if the request has no token 588 */ 589 public static UserGroupInformation getUGI(ServletContext context, 590 HttpServletRequest request, Configuration conf, 591 final AuthenticationMethod secureAuthMethod, 592 final boolean tryUgiParameter) throws IOException { 593 UserGroupInformation ugi = null; 594 final String usernameFromQuery = getUsernameFromQuery(request, tryUgiParameter); 595 final String doAsUserFromQuery = request.getParameter(DoAsParam.NAME); 596 final String remoteUser; 597 598 if (UserGroupInformation.isSecurityEnabled()) { 599 remoteUser = request.getRemoteUser(); 600 final String tokenString = request.getParameter(DELEGATION_PARAMETER_NAME); 601 if (tokenString != null) { 602 // Token-based connections need only verify the effective user, and 603 // disallow proxying to different user. Proxy authorization checks 604 // are not required since the checks apply to issuing a token. 605 ugi = getTokenUGI(context, request, tokenString, conf); 606 checkUsername(ugi.getShortUserName(), usernameFromQuery); 607 checkUsername(ugi.getShortUserName(), doAsUserFromQuery); 608 } else if (remoteUser == null) { 609 throw new IOException( 610 "Security enabled but user not authenticated by filter"); 611 } 612 } else { 613 // Security's not on, pull from url or use default web user 614 remoteUser = (usernameFromQuery == null) 615 ? getDefaultWebUserName(conf) // not specified in request 616 : usernameFromQuery; 617 } 618 619 if (ugi == null) { // security is off, or there's no token 620 ugi = UserGroupInformation.createRemoteUser(remoteUser); 621 checkUsername(ugi.getShortUserName(), usernameFromQuery); 622 if (UserGroupInformation.isSecurityEnabled()) { 623 // This is not necessarily true, could have been auth'ed by user-facing 624 // filter 625 ugi.setAuthenticationMethod(secureAuthMethod); 626 } 627 if (doAsUserFromQuery != null) { 628 // create and attempt to authorize a proxy user 629 ugi = UserGroupInformation.createProxyUser(doAsUserFromQuery, ugi); 630 ProxyUsers.authorize(ugi, getRemoteAddr(request)); 631 } 632 } 633 634 if(LOG.isDebugEnabled()) 635 LOG.debug("getUGI is returning: " + ugi.getShortUserName()); 636 return ugi; 637 } 638 639 private static UserGroupInformation getTokenUGI(ServletContext context, 640 HttpServletRequest request, 641 String tokenString, 642 Configuration conf) 643 throws IOException { 644 final Token<DelegationTokenIdentifier> token = 645 new Token<DelegationTokenIdentifier>(); 646 token.decodeFromUrlString(tokenString); 647 InetSocketAddress serviceAddress = getNNServiceAddress(context, request); 648 if (serviceAddress != null) { 649 SecurityUtil.setTokenService(token, serviceAddress); 650 token.setKind(DelegationTokenIdentifier.HDFS_DELEGATION_KIND); 651 } 652 653 ByteArrayInputStream buf = 654 new ByteArrayInputStream(token.getIdentifier()); 655 DataInputStream in = new DataInputStream(buf); 656 DelegationTokenIdentifier id = new DelegationTokenIdentifier(); 657 id.readFields(in); 658 if (context != null) { 659 final NameNode nn = NameNodeHttpServer.getNameNodeFromContext(context); 660 if (nn != null) { 661 // Verify the token. 662 nn.getNamesystem().verifyToken(id, token.getPassword()); 663 } 664 } 665 UserGroupInformation ugi = id.getUser(); 666 ugi.addToken(token); 667 return ugi; 668 } 669 670 // honor the X-Forwarded-For header set by a configured set of trusted 671 // proxy servers. allows audit logging and proxy user checks to work 672 // via an http proxy 673 public static String getRemoteAddr(HttpServletRequest request) { 674 String remoteAddr = request.getRemoteAddr(); 675 String proxyHeader = request.getHeader("X-Forwarded-For"); 676 if (proxyHeader != null && ProxyServers.isProxyServer(remoteAddr)) { 677 final String clientAddr = proxyHeader.split(",")[0].trim(); 678 if (!clientAddr.isEmpty()) { 679 remoteAddr = clientAddr; 680 } 681 } 682 return remoteAddr; 683 } 684 685 686 /** 687 * Expected user name should be a short name. 688 */ 689 private static void checkUsername(final String expected, final String name 690 ) throws IOException { 691 if (expected == null && name != null) { 692 throw new IOException("Usernames not matched: expecting null but name=" 693 + name); 694 } 695 if (name == null) { //name is optional, null is okay 696 return; 697 } 698 KerberosName u = new KerberosName(name); 699 String shortName = u.getShortName(); 700 if (!shortName.equals(expected)) { 701 throw new IOException("Usernames not matched: name=" + shortName 702 + " != expected=" + expected); 703 } 704 } 705 706 private static String getUsernameFromQuery(final HttpServletRequest request, 707 final boolean tryUgiParameter) { 708 String username = request.getParameter(UserParam.NAME); 709 if (username == null && tryUgiParameter) { 710 //try ugi parameter 711 final String ugiStr = request.getParameter("ugi"); 712 if (ugiStr != null) { 713 username = ugiStr.split(",")[0]; 714 } 715 } 716 return username; 717 } 718 719 /** 720 * Returns the url parameter for the given token string. 721 * @param tokenString 722 * @return url parameter 723 */ 724 public static String getDelegationTokenUrlParam(String tokenString) { 725 if (tokenString == null ) { 726 return ""; 727 } 728 if (UserGroupInformation.isSecurityEnabled()) { 729 return SET_DELEGATION + tokenString; 730 } else { 731 return ""; 732 } 733 } 734 735 /** 736 * Returns the url parameter for the given string, prefixed with 737 * paramSeparator. 738 * 739 * @param name parameter name 740 * @param val parameter value 741 * @param paramSeparator URL parameter prefix, i.e. either '?' or '&' 742 * @return url parameter 743 */ 744 public static String getUrlParam(String name, String val, String paramSeparator) { 745 return val == null ? "" : paramSeparator + name + "=" + val; 746 } 747 748 /** 749 * Returns the url parameter for the given string, prefixed with '?' if 750 * firstParam is true, prefixed with '&' if firstParam is false. 751 * 752 * @param name parameter name 753 * @param val parameter value 754 * @param firstParam true if this is the first parameter in the list, false otherwise 755 * @return url parameter 756 */ 757 public static String getUrlParam(String name, String val, boolean firstParam) { 758 return getUrlParam(name, val, firstParam ? "?" : "&"); 759 } 760 761 /** 762 * Returns the url parameter for the given string, prefixed with '&'. 763 * 764 * @param name parameter name 765 * @param val parameter value 766 * @return url parameter 767 */ 768 public static String getUrlParam(String name, String val) { 769 return getUrlParam(name, val, false); 770 } 771}