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 }