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