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 */
018package org.apache.hadoop.hbase.http;
019
020import org.apache.hadoop.conf.Configuration;
021import org.apache.hadoop.security.authorize.AuthorizationException;
022import org.apache.hadoop.security.authorize.ProxyUsers;
023import org.apache.hadoop.security.UserGroupInformation;
024import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
025import org.apache.hadoop.util.HttpExceptionUtils;
026import org.apache.hadoop.util.StringUtils;
027import org.apache.yetus.audience.InterfaceAudience;
028import org.apache.yetus.audience.InterfaceStability;
029import org.slf4j.Logger;
030import org.slf4j.LoggerFactory;
031
032import java.io.IOException;
033import java.security.Principal;
034import java.util.ArrayList;
035import java.util.Enumeration;
036import java.util.HashMap;
037import java.util.Iterator;
038import java.util.List;
039import java.util.Map;
040import javax.servlet.FilterChain;
041import javax.servlet.FilterConfig;
042import javax.servlet.ServletException;
043import javax.servlet.http.HttpServletRequest;
044import javax.servlet.http.HttpServletRequestWrapper;
045import javax.servlet.http.HttpServletResponse;
046
047/**
048 * This file has been copied directly (changing only the package name and and the ASF license
049 * text format, and adding the Yetus annotations) from Hadoop, as the Hadoop version that HBase
050 * depends on doesn't have it yet
051 * (as of 2020 Apr 24, there is no Hadoop release that has it either).
052 *
053 * Hadoop version:
054 * unreleased, master branch commit 4ea6c2f457496461afc63f38ef4cef3ab0efce49
055 *
056 * Haddop path:
057 * hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/security/authentication/
058 * server/ProxyUserAuthenticationFilter.java
059 *
060 * AuthenticationFilter which adds support to perform operations
061 * using end user instead of proxy user. Fetches the end user from
062 * doAs Query Parameter.
063 */
064@InterfaceAudience.Private
065@InterfaceStability.Evolving
066public class ProxyUserAuthenticationFilter extends AuthenticationFilter {
067
068  private static final Logger LOG = LoggerFactory.getLogger(
069      ProxyUserAuthenticationFilter.class);
070
071  private static final String DO_AS = "doas";
072  public static final String PROXYUSER_PREFIX = "proxyuser";
073
074  @Override
075  public void init(FilterConfig filterConfig) throws ServletException {
076    Configuration conf = getProxyuserConfiguration(filterConfig);
077    ProxyUsers.refreshSuperUserGroupsConfiguration(conf, PROXYUSER_PREFIX);
078    super.init(filterConfig);
079  }
080
081  @Override
082  protected void doFilter(FilterChain filterChain, HttpServletRequest request,
083      HttpServletResponse response) throws IOException, ServletException {
084    final HttpServletRequest lowerCaseRequest = toLowerCase(request);
085    String doAsUser = lowerCaseRequest.getParameter(DO_AS);
086
087    if (doAsUser != null && !doAsUser.equals(request.getRemoteUser())) {
088      LOG.debug("doAsUser = {}, RemoteUser = {} , RemoteAddress = {} ",
089          doAsUser, request.getRemoteUser(), request.getRemoteAddr());
090      UserGroupInformation requestUgi = (request.getUserPrincipal() != null) ?
091          UserGroupInformation.createRemoteUser(request.getRemoteUser())
092          : null;
093      if (requestUgi != null) {
094        requestUgi = UserGroupInformation.createProxyUser(doAsUser,
095            requestUgi);
096        try {
097          ProxyUsers.authorize(requestUgi, request.getRemoteAddr());
098
099          final UserGroupInformation ugiF = requestUgi;
100          request = new HttpServletRequestWrapper(request) {
101            @Override
102            public String getRemoteUser() {
103              return ugiF.getShortUserName();
104            }
105
106            @Override
107            public Principal getUserPrincipal() {
108              return new Principal() {
109                @Override
110                public String getName() {
111                  return ugiF.getUserName();
112                }
113              };
114            }
115          };
116          LOG.debug("Proxy user Authentication successful");
117        } catch (AuthorizationException ex) {
118          HttpExceptionUtils.createServletExceptionResponse(response,
119              HttpServletResponse.SC_FORBIDDEN, ex);
120          LOG.warn("Proxy user Authentication exception", ex);
121          return;
122        }
123      }
124    }
125    super.doFilter(filterChain, request, response);
126  }
127
128  protected Configuration getProxyuserConfiguration(FilterConfig filterConfig)
129      throws ServletException {
130    Configuration conf = new Configuration(false);
131    Enumeration<?> names = filterConfig.getInitParameterNames();
132    while (names.hasMoreElements()) {
133      String name = (String) names.nextElement();
134      if (name.startsWith(PROXYUSER_PREFIX + ".")) {
135        String value = filterConfig.getInitParameter(name);
136        conf.set(name, value);
137      }
138    }
139    return conf;
140  }
141
142  static boolean containsUpperCase(final Iterable<String> strings) {
143    for(String s : strings) {
144      for(int i = 0; i < s.length(); i++) {
145        if (Character.isUpperCase(s.charAt(i))) {
146          return true;
147        }
148      }
149    }
150    return false;
151  }
152
153  public static HttpServletRequest toLowerCase(
154      final HttpServletRequest request) {
155    @SuppressWarnings("unchecked")
156    final Map<String, String[]> original = (Map<String, String[]>)
157        request.getParameterMap();
158    if (!containsUpperCase(original.keySet())) {
159      return request;
160    }
161
162    final Map<String, List<String>> m = new HashMap<String, List<String>>();
163    for (Map.Entry<String, String[]> entry : original.entrySet()) {
164      final String key = StringUtils.toLowerCase(entry.getKey());
165      List<String> strings = m.get(key);
166      if (strings == null) {
167        strings = new ArrayList<String>();
168        m.put(key, strings);
169      }
170      for (String v : entry.getValue()) {
171        strings.add(v);
172      }
173    }
174
175    return new HttpServletRequestWrapper(request) {
176      private Map<String, String[]> parameters = null;
177
178      @Override
179      public Map<String, String[]> getParameterMap() {
180        if (parameters == null) {
181          parameters = new HashMap<String, String[]>();
182          for (Map.Entry<String, List<String>> entry : m.entrySet()) {
183            final List<String> a = entry.getValue();
184            parameters.put(entry.getKey(), a.toArray(new String[a.size()]));
185          }
186        }
187        return parameters;
188      }
189
190      @Override
191      public String getParameter(String name) {
192        final List<String> a = m.get(name);
193        return a == null ? null : a.get(0);
194      }
195
196      @Override
197      public String[] getParameterValues(String name) {
198        return getParameterMap().get(name);
199      }
200
201      @Override
202      public Enumeration<String> getParameterNames() {
203        final Iterator<String> i = m.keySet().iterator();
204        return new Enumeration<String>() {
205          @Override
206          public boolean hasMoreElements() {
207            return i.hasNext();
208          }
209
210          @Override
211          public String nextElement() {
212            return i.next();
213          }
214        };
215      }
216    };
217  }
218
219}