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