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