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.GETDELEGATIONTOKENS.toString());
068        DELEGATION_TOKEN_OPS.add(
069          DelegationTokenOperation.RENEWDELEGATIONTOKEN.toString());
070        DELEGATION_TOKEN_OPS.add(
071          DelegationTokenOperation.CANCELDELEGATIONTOKEN.toString());
072      }
073    
074      public static final String TYPE = "kerberos-dt";
075    
076      /**
077       * Returns authentication type of the handler.
078       *
079       * @return <code>delegationtoken-kerberos</code>
080       */
081      @Override
082      public String getType() {
083        return TYPE;
084      }
085    
086      private static final String ENTER = System.getProperty("line.separator");
087    
088      @Override
089      @SuppressWarnings("unchecked")
090      public boolean managementOperation(AuthenticationToken token,
091          HttpServletRequest request, HttpServletResponse response)
092        throws IOException, AuthenticationException {
093        boolean requestContinues = true;
094        String op = request.getParameter(HttpFSFileSystem.OP_PARAM);
095        op = (op != null) ? op.toUpperCase() : null;
096        if (DELEGATION_TOKEN_OPS.contains(op) &&
097            !request.getMethod().equals("OPTIONS")) {
098          DelegationTokenOperation dtOp =
099            DelegationTokenOperation.valueOf(op);
100          if (dtOp.getHttpMethod().equals(request.getMethod())) {
101            if (dtOp.requiresKerberosCredentials() && token == null) {
102              response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
103                MessageFormat.format(
104                  "Operation [{0}] requires SPNEGO authentication established",
105                  dtOp));
106              requestContinues = false;
107            } else {
108              DelegationTokenManager tokenManager =
109                HttpFSServerWebApp.get().get(DelegationTokenManager.class);
110              try {
111                Map map = null;
112                switch (dtOp) {
113                  case GETDELEGATIONTOKEN:
114                  case GETDELEGATIONTOKENS:
115                    String renewerParam =
116                      request.getParameter(HttpFSKerberosAuthenticator.RENEWER_PARAM);
117                    if (renewerParam == null) {
118                      renewerParam = token.getUserName();
119                    }
120                    Token<?> dToken = tokenManager.createToken(
121                      UserGroupInformation.getCurrentUser(), renewerParam);
122                    if (dtOp == DelegationTokenOperation.GETDELEGATIONTOKEN) {
123                      map = delegationTokenToJSON(dToken);
124                    } else {
125                      map = delegationTokensToJSON(Arrays.asList((Token)dToken));
126                    }
127                    break;
128                  case RENEWDELEGATIONTOKEN:
129                  case CANCELDELEGATIONTOKEN:
130                    String tokenParam =
131                      request.getParameter(HttpFSKerberosAuthenticator.TOKEN_PARAM);
132                    if (tokenParam == null) {
133                      response.sendError(HttpServletResponse.SC_BAD_REQUEST,
134                        MessageFormat.format(
135                          "Operation [{0}] requires the parameter [{1}]",
136                          dtOp, HttpFSKerberosAuthenticator.TOKEN_PARAM));
137                      requestContinues = false;
138                    } else {
139                      if (dtOp == DelegationTokenOperation.CANCELDELEGATIONTOKEN) {
140                        Token<DelegationTokenIdentifier> dt =
141                          new Token<DelegationTokenIdentifier>();
142                        dt.decodeFromUrlString(tokenParam);
143                        tokenManager.cancelToken(dt,
144                          UserGroupInformation.getCurrentUser().getUserName());
145                      } else {
146                        Token<DelegationTokenIdentifier> dt =
147                          new Token<DelegationTokenIdentifier>();
148                        dt.decodeFromUrlString(tokenParam);
149                        long expirationTime =
150                          tokenManager.renewToken(dt, token.getUserName());
151                        map = new HashMap();
152                        map.put("long", expirationTime);
153                      }
154                    }
155                    break;
156                }
157                if (requestContinues) {
158                  response.setStatus(HttpServletResponse.SC_OK);
159                  if (map != null) {
160                    response.setContentType(MediaType.APPLICATION_JSON);
161                    Writer writer = response.getWriter();
162                    JSONObject.writeJSONString(map, writer);
163                    writer.write(ENTER);
164                    writer.flush();
165    
166                  }
167                  requestContinues = false;
168                }
169              } catch (DelegationTokenManagerException ex) {
170                throw new AuthenticationException(ex.toString(), ex);
171              }
172            }
173          } else {
174            response.sendError(HttpServletResponse.SC_BAD_REQUEST,
175              MessageFormat.format(
176                "Wrong HTTP method [{0}] for operation [{1}], it should be [{2}]",
177                request.getMethod(), dtOp, dtOp.getHttpMethod()));
178            requestContinues = false;
179          }
180        }
181        return requestContinues;
182      }
183    
184      @SuppressWarnings("unchecked")
185      private static Map delegationTokenToJSON(Token token) throws IOException {
186        Map json = new LinkedHashMap();
187        json.put(HttpFSKerberosAuthenticator.DELEGATION_TOKEN_URL_STRING_JSON,
188                 token.encodeToUrlString());
189        Map response = new LinkedHashMap();
190        response.put(HttpFSKerberosAuthenticator.DELEGATION_TOKEN_JSON, json);
191        return response;
192      }
193      
194      @SuppressWarnings("unchecked")
195      private static Map delegationTokensToJSON(List<Token> tokens)
196        throws IOException {
197        List list = new ArrayList();
198        for (Token token : tokens) {
199          Map map = new HashMap();
200          map.put(HttpFSKerberosAuthenticator.DELEGATION_TOKEN_URL_STRING_JSON,
201                  token.encodeToUrlString());
202          list.add(map);
203        }
204        Map map = new HashMap();
205        map.put(HttpFSKerberosAuthenticator.DELEGATION_TOKEN_JSON, list);
206        Map response = new LinkedHashMap();
207        response.put(HttpFSKerberosAuthenticator.DELEGATION_TOKENS_JSON, map);
208        return response;
209      }
210    
211      /**
212       * Authenticates a request looking for the <code>delegation</code>
213       * query-string parameter and verifying it is a valid token. If there is not
214       * <code>delegation</code> query-string parameter, it delegates the
215       * authentication to the {@link KerberosAuthenticationHandler} unless it is
216       * disabled.
217       *
218       * @param request the HTTP client request.
219       * @param response the HTTP client response.
220       *
221       * @return the authentication token for the authenticated request.
222       * @throws IOException thrown if an IO error occurred.
223       * @throws AuthenticationException thrown if the authentication failed.
224       */
225      @Override
226      public AuthenticationToken authenticate(HttpServletRequest request,
227                                              HttpServletResponse response)
228        throws IOException, AuthenticationException {
229        AuthenticationToken token;
230        String delegationParam =
231          request.getParameter(HttpFSKerberosAuthenticator.DELEGATION_PARAM);
232        if (delegationParam != null) {
233          try {
234            Token<DelegationTokenIdentifier> dt =
235              new Token<DelegationTokenIdentifier>();
236            dt.decodeFromUrlString(delegationParam);
237            DelegationTokenManager tokenManager =
238              HttpFSServerWebApp.get().get(DelegationTokenManager.class);
239            UserGroupInformation ugi = tokenManager.verifyToken(dt);
240            final String shortName = ugi.getShortUserName();
241    
242            // creating a ephemeral token
243            token = new AuthenticationToken(shortName, ugi.getUserName(),
244                                            getType());
245            token.setExpires(0);
246          } catch (Throwable ex) {
247            throw new AuthenticationException("Could not verify DelegationToken, " +
248                                              ex.toString(), ex);
249          }
250        } else {
251          token = super.authenticate(request, response);
252        }
253        return token;
254      }
255    
256    
257    }