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.InetSocketAddress;
029import java.net.Socket;
030import java.net.URL;
031import java.net.URLEncoder;
032import java.util.Arrays;
033import java.util.Collections;
034import java.util.Comparator;
035import java.util.HashMap;
036import java.util.List;
037
038import javax.servlet.ServletContext;
039import javax.servlet.http.HttpServletRequest;
040import javax.servlet.jsp.JspWriter;
041
042import org.apache.commons.logging.Log;
043import org.apache.commons.logging.LogFactory;
044import org.apache.hadoop.classification.InterfaceAudience;
045import org.apache.hadoop.conf.Configuration;
046import org.apache.hadoop.fs.Path;
047import org.apache.hadoop.hdfs.BlockReader;
048import org.apache.hadoop.hdfs.BlockReaderFactory;
049import org.apache.hadoop.hdfs.ClientContext;
050import org.apache.hadoop.hdfs.DFSClient;
051import org.apache.hadoop.hdfs.DFSUtil;
052import org.apache.hadoop.hdfs.RemotePeerFactory;
053import org.apache.hadoop.hdfs.net.Peer;
054import org.apache.hadoop.hdfs.net.TcpPeerServer;
055import org.apache.hadoop.hdfs.protocol.DatanodeID;
056import org.apache.hadoop.hdfs.protocol.DatanodeInfo;
057import org.apache.hadoop.hdfs.protocol.ExtendedBlock;
058import org.apache.hadoop.hdfs.protocol.LocatedBlock;
059import org.apache.hadoop.hdfs.protocol.LocatedBlocks;
060import org.apache.hadoop.hdfs.security.token.block.BlockTokenIdentifier;
061import org.apache.hadoop.hdfs.security.token.block.DataEncryptionKey;
062import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier;
063import org.apache.hadoop.hdfs.server.blockmanagement.DatanodeDescriptor;
064import org.apache.hadoop.hdfs.server.datanode.CachingStrategy;
065import org.apache.hadoop.hdfs.server.namenode.NameNode;
066import org.apache.hadoop.hdfs.server.namenode.NameNodeHttpServer;
067import org.apache.hadoop.hdfs.web.resources.DelegationParam;
068import org.apache.hadoop.hdfs.web.resources.DoAsParam;
069import org.apache.hadoop.hdfs.web.resources.UserParam;
070import org.apache.hadoop.http.HtmlQuoting;
071import org.apache.hadoop.io.IOUtils;
072import org.apache.hadoop.net.NetUtils;
073import org.apache.hadoop.security.AccessControlException;
074import org.apache.hadoop.security.SecurityUtil;
075import org.apache.hadoop.security.UserGroupInformation;
076import org.apache.hadoop.security.UserGroupInformation.AuthenticationMethod;
077import org.apache.hadoop.security.authentication.util.KerberosName;
078import org.apache.hadoop.security.authorize.ProxyUsers;
079import org.apache.hadoop.security.token.Token;
080import org.apache.hadoop.util.VersionInfo;
081
082import com.google.common.base.Charsets;
083
084@InterfaceAudience.Private
085public 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}