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