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    package org.apache.hadoop.fs.http.server;
019    
020    import org.apache.hadoop.classification.InterfaceAudience;
021    import org.apache.hadoop.fs.http.client.HttpFSFileSystem;
022    import org.apache.hadoop.fs.http.client.HttpFSKerberosAuthenticator;
023    import org.apache.hadoop.fs.http.client.HttpFSKerberosAuthenticator.DelegationTokenOperation;
024    import org.apache.hadoop.lib.service.DelegationTokenIdentifier;
025    import org.apache.hadoop.lib.service.DelegationTokenManager;
026    import org.apache.hadoop.lib.service.DelegationTokenManagerException;
027    import org.apache.hadoop.security.UserGroupInformation;
028    import org.apache.hadoop.security.authentication.client.AuthenticationException;
029    import org.apache.hadoop.security.authentication.server.AuthenticationToken;
030    import org.apache.hadoop.security.authentication.server.KerberosAuthenticationHandler;
031    import org.apache.hadoop.security.token.Token;
032    import org.json.simple.JSONObject;
033    
034    import javax.servlet.http.HttpServletRequest;
035    import javax.servlet.http.HttpServletResponse;
036    import javax.ws.rs.core.MediaType;
037    import java.io.IOException;
038    import java.io.Writer;
039    import java.text.MessageFormat;
040    import java.util.ArrayList;
041    import java.util.Arrays;
042    import java.util.HashMap;
043    import java.util.HashSet;
044    import java.util.LinkedHashMap;
045    import java.util.List;
046    import java.util.Map;
047    import java.util.Set;
048    
049    /**
050     * Server side <code>AuthenticationHandler</code> that authenticates requests
051     * using the incoming delegation token as a 'delegation' query string parameter.
052     * <p/>
053     * If not delegation token is present in the request it delegates to the
054     * {@link KerberosAuthenticationHandler}
055     */
056    @InterfaceAudience.Private
057    public class HttpFSKerberosAuthenticationHandler
058      extends KerberosAuthenticationHandler {
059    
060      static final Set<String> DELEGATION_TOKEN_OPS =
061        new HashSet<String>();
062    
063      static {
064        DELEGATION_TOKEN_OPS.add(
065          DelegationTokenOperation.GETDELEGATIONTOKEN.toString());
066        DELEGATION_TOKEN_OPS.add(
067          DelegationTokenOperation.RENEWDELEGATIONTOKEN.toString());
068        DELEGATION_TOKEN_OPS.add(
069          DelegationTokenOperation.CANCELDELEGATIONTOKEN.toString());
070      }
071    
072      public static final String TYPE = "kerberos-dt";
073    
074      /**
075       * Returns authentication type of the handler.
076       *
077       * @return <code>delegationtoken-kerberos</code>
078       */
079      @Override
080      public String getType() {
081        return TYPE;
082      }
083    
084      private static final String ENTER = System.getProperty("line.separator");
085    
086      @Override
087      @SuppressWarnings("unchecked")
088      public boolean managementOperation(AuthenticationToken token,
089          HttpServletRequest request, HttpServletResponse response)
090        throws IOException, AuthenticationException {
091        boolean requestContinues = true;
092        String op = request.getParameter(HttpFSFileSystem.OP_PARAM);
093        op = (op != null) ? op.toUpperCase() : null;
094        if (DELEGATION_TOKEN_OPS.contains(op) &&
095            !request.getMethod().equals("OPTIONS")) {
096          DelegationTokenOperation dtOp =
097            DelegationTokenOperation.valueOf(op);
098          if (dtOp.getHttpMethod().equals(request.getMethod())) {
099            if (dtOp.requiresKerberosCredentials() && token == null) {
100              response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
101                MessageFormat.format(
102                  "Operation [{0}] requires SPNEGO authentication established",
103                  dtOp));
104              requestContinues = false;
105            } else {
106              DelegationTokenManager tokenManager =
107                HttpFSServerWebApp.get().get(DelegationTokenManager.class);
108              try {
109                Map map = null;
110                switch (dtOp) {
111                  case GETDELEGATIONTOKEN:
112                    String renewerParam =
113                      request.getParameter(HttpFSKerberosAuthenticator.RENEWER_PARAM);
114                    if (renewerParam == null) {
115                      renewerParam = token.getUserName();
116                    }
117                    Token<?> dToken = tokenManager.createToken(
118                      UserGroupInformation.getCurrentUser(), renewerParam);
119                    map = delegationTokenToJSON(dToken);
120                    break;
121                  case RENEWDELEGATIONTOKEN:
122                  case CANCELDELEGATIONTOKEN:
123                    String tokenParam =
124                      request.getParameter(HttpFSKerberosAuthenticator.TOKEN_PARAM);
125                    if (tokenParam == null) {
126                      response.sendError(HttpServletResponse.SC_BAD_REQUEST,
127                        MessageFormat.format(
128                          "Operation [{0}] requires the parameter [{1}]",
129                          dtOp, HttpFSKerberosAuthenticator.TOKEN_PARAM));
130                      requestContinues = false;
131                    } else {
132                      if (dtOp == DelegationTokenOperation.CANCELDELEGATIONTOKEN) {
133                        Token<DelegationTokenIdentifier> dt =
134                          new Token<DelegationTokenIdentifier>();
135                        dt.decodeFromUrlString(tokenParam);
136                        tokenManager.cancelToken(dt,
137                          UserGroupInformation.getCurrentUser().getUserName());
138                      } else {
139                        Token<DelegationTokenIdentifier> dt =
140                          new Token<DelegationTokenIdentifier>();
141                        dt.decodeFromUrlString(tokenParam);
142                        long expirationTime =
143                          tokenManager.renewToken(dt, token.getUserName());
144                        map = new HashMap();
145                        map.put("long", expirationTime);
146                      }
147                    }
148                    break;
149                }
150                if (requestContinues) {
151                  response.setStatus(HttpServletResponse.SC_OK);
152                  if (map != null) {
153                    response.setContentType(MediaType.APPLICATION_JSON);
154                    Writer writer = response.getWriter();
155                    JSONObject.writeJSONString(map, writer);
156                    writer.write(ENTER);
157                    writer.flush();
158    
159                  }
160                  requestContinues = false;
161                }
162              } catch (DelegationTokenManagerException ex) {
163                throw new AuthenticationException(ex.toString(), ex);
164              }
165            }
166          } else {
167            response.sendError(HttpServletResponse.SC_BAD_REQUEST,
168              MessageFormat.format(
169                "Wrong HTTP method [{0}] for operation [{1}], it should be [{2}]",
170                request.getMethod(), dtOp, dtOp.getHttpMethod()));
171            requestContinues = false;
172          }
173        }
174        return requestContinues;
175      }
176    
177      @SuppressWarnings("unchecked")
178      private static Map delegationTokenToJSON(Token token) throws IOException {
179        Map json = new LinkedHashMap();
180        json.put(HttpFSKerberosAuthenticator.DELEGATION_TOKEN_URL_STRING_JSON,
181                 token.encodeToUrlString());
182        Map response = new LinkedHashMap();
183        response.put(HttpFSKerberosAuthenticator.DELEGATION_TOKEN_JSON, json);
184        return response;
185      }
186      
187      /**
188       * Authenticates a request looking for the <code>delegation</code>
189       * query-string parameter and verifying it is a valid token. If there is not
190       * <code>delegation</code> query-string parameter, it delegates the
191       * authentication to the {@link KerberosAuthenticationHandler} unless it is
192       * disabled.
193       *
194       * @param request the HTTP client request.
195       * @param response the HTTP client response.
196       *
197       * @return the authentication token for the authenticated request.
198       * @throws IOException thrown if an IO error occurred.
199       * @throws AuthenticationException thrown if the authentication failed.
200       */
201      @Override
202      public AuthenticationToken authenticate(HttpServletRequest request,
203                                              HttpServletResponse response)
204        throws IOException, AuthenticationException {
205        AuthenticationToken token;
206        String delegationParam =
207          request.getParameter(HttpFSKerberosAuthenticator.DELEGATION_PARAM);
208        if (delegationParam != null) {
209          try {
210            Token<DelegationTokenIdentifier> dt =
211              new Token<DelegationTokenIdentifier>();
212            dt.decodeFromUrlString(delegationParam);
213            DelegationTokenManager tokenManager =
214              HttpFSServerWebApp.get().get(DelegationTokenManager.class);
215            UserGroupInformation ugi = tokenManager.verifyToken(dt);
216            final String shortName = ugi.getShortUserName();
217    
218            // creating a ephemeral token
219            token = new AuthenticationToken(shortName, ugi.getUserName(),
220                                            getType());
221            token.setExpires(0);
222          } catch (Throwable ex) {
223            throw new AuthenticationException("Could not verify DelegationToken, " +
224                                              ex.toString(), ex);
225          }
226        } else {
227          token = super.authenticate(request, response);
228        }
229        return token;
230      }
231    
232    
233    }