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 }