io.fabric8.apiman.rest.BearerTokenFilter.java Source code

Java tutorial

Introduction

Here is the source code for io.fabric8.apiman.rest.BearerTokenFilter.java

Source

/*
 * Copyright 2015 JBoss Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.fabric8.apiman.rest;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import io.apiman.common.auth.AuthPrincipal;
import io.fabric8.apiman.AuthToken;
import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.openshift.api.model.SubjectAccessReview;
import io.fabric8.openshift.api.model.SubjectAccessReviewBuilder;
import io.fabric8.openshift.api.model.SubjectAccessReviewResponse;
import io.fabric8.openshift.api.model.User;
import io.fabric8.openshift.client.DefaultOpenShiftClient;
import io.fabric8.openshift.client.NamespacedOpenShiftClient;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.Principal;
import java.util.concurrent.TimeUnit;

import static io.fabric8.kubernetes.client.utils.Utils.getSystemPropertyOrEnvVar;

/**
 * A simple implementation of an bearer token filter that checks the validity of
 * an incoming bearer token with the OpenShift issuer. The OpenShift call
 * returns a JSON from which the UserPrincipal can be set.
 */
public class BearerTokenFilter implements Filter {

    public static final String BEARER_TOKEN_TTL = "BEARER_TOKEN_CACHE_TTL";
    public static final String BEARER_TOKEN_CACHE_MAXSIZE = "BEARER_TOKEN_CACHE_MAXSIZE";

    private static LoadingCache<String, UserInfo> bearerTokenCache = null;

    final private static Log log = LogFactory.getLog(BearerTokenFilter.class);

    /**
     * Constructor.
     */
    public BearerTokenFilter() {
    }

    /**
     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
     */
    @Override
    public void init(FilterConfig config) throws ServletException {
        // maximum 10000 tokens in the cache
        Number bearerTokenCacheMaxsize = getSystemPropertyOrEnvVar(BEARER_TOKEN_CACHE_MAXSIZE, 10000);
        // cache for 10  min
        Number bearerTokenTTL = getSystemPropertyOrEnvVar(BEARER_TOKEN_TTL, 10);

        bearerTokenCache = CacheBuilder.newBuilder().concurrencyLevel(4) // allowed concurrency among update operations
                .maximumSize(bearerTokenCacheMaxsize.longValue())
                .expireAfterWrite(bearerTokenTTL.longValue(), TimeUnit.MINUTES)
                .build(new CacheLoader<String, UserInfo>() {
                    public UserInfo load(String authToken) throws Exception {
                        return getUserInfoFromK8s(authToken);
                    }
                });
    }

    /**
     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
     *      javax.servlet.ServletResponse, javax.servlet.FilterChain)
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        String authHeader = req.getHeader("Authorization");
        AuthToken.set(null);

        if (authHeader != null && authHeader.toUpperCase().startsWith("BEARER")) {
            //validate token with issuer
            try {
                String authToken = authHeader.substring(7);
                UserInfo userInfo = bearerTokenCache.get(authToken);
                AuthToken.set(authToken);
                AuthPrincipal principal = new AuthPrincipal(userInfo.username);
                principal.addRole("apiuser");
                if (userInfo.isClusterAdmin) {
                    principal.addRole("apiadmin");
                }
                request = wrapTheRequest(request, principal);
                chain.doFilter(request, response);
            } catch (Exception e) {
                e.printStackTrace();
                String errMsg = e.getMessage();
                if (e.getMessage().contains("Server returned HTTP response code")) {
                    errMsg = "Invalid BearerToken";
                } else {
                    errMsg = "Cannot validate BearerToken";
                    log.error(errMsg, e);
                }
                sendInvalidTokenResponse((HttpServletResponse) response, errMsg);
            }
        } else if (("/".equals(req.getPathInfo())) || ("/swagger.json".equals(req.getPathInfo()))
                || ("/config.js".equals(req.getPathInfo())) || ("/swagger.yaml".equals(req.getPathInfo()))
                || (req.getPathInfo().startsWith("/downloads/"))) {
            //allow anonymous requests to the root or swagger document
            log.debug("Allowing anonymous access to " + req.getPathInfo());
            chain.doFilter(request, response);
        } else {
            //no bearer token present
            String loginForm = "<html>\n" + "<title>Login into APIMAN with authToken</title>\n" + "<head>\n"
                    + "</head>\n" + "<body>\n"
                    + "<h1>Provide valid authToken and a link into the Apiman console</h1>\n"
                    + "<form action=\"/apimanui/link\" method=\"post\">\n"
                    + "    AuthToken: <input type=\"text\" name=\"access_token\" size=\"100\" value=\"oc whoami -t\" /></br>\n"
                    + "    Redirect URL: <input type=\"text\" name=\"redirect\"  size=\"100\" value=\"/apimanui/api-manager/\" /></br>\n"
                    + "    <input type=\"submit\" value=\"Login\" />    \n" + "</form>\n" + "</body>\n" + "</html>";
            response.setCharacterEncoding("UTF-8");
            response.getWriter().append(loginForm);
            //sendInvalidTokenResponse((HttpServletResponse)response, "No BearerToken");
        }
    }

    /**
     * Wrap the request to provide the principal.
     *
     * @param request
     *            the request
     * @param principal
     *            the principal
     */
    private HttpServletRequest wrapTheRequest(final ServletRequest request, final AuthPrincipal principal) {
        HttpServletRequestWrapper wrapper = new HttpServletRequestWrapper((HttpServletRequest) request) {
            @Override
            public Principal getUserPrincipal() {
                return principal;
            }

            @Override
            public boolean isUserInRole(String role) {
                return principal.getRoles().contains(role);
            }

            @Override
            public String getRemoteUser() {
                return principal.getName();
            }
        };
        return wrapper;
    }

    /**
     * Sends a response that tells the client that authentication is required.
     *
     * @param response
     *            the response
     * @throws IOException
     *             when an error cannot be sent
     */
    private void sendInvalidTokenResponse(HttpServletResponse response, String errMsg) throws IOException {

        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, errMsg);
    }

    /**
     * @see javax.servlet.Filter#destroy()
     */
    @Override
    public void destroy() {
    }

    /**
     * Given a token is
     * @param token
     * @return
     */
    public UserInfo getUserInfoFromK8s(final String token) {
        if (log.isDebugEnabled())
            log.debug("Calling k8s to validate header abd obtain username and role for token " + token);
        UserInfo userInfo = new UserInfo();
        ConfigBuilder builder = new ConfigBuilder().withOauthToken(token);
        NamespacedOpenShiftClient osClient = null;
        try {
            osClient = new DefaultOpenShiftClient(builder.build());
            //get the userName of the current user
            User user = osClient.inAnyNamespace().users().withName("~").get();
            if (log.isDebugEnabled())
                log.debug("user: " + user);
            //check to see if this user has the clusterAdmin role
            SubjectAccessReview request = new SubjectAccessReviewBuilder().withVerb("*").withResource("*")
                    .withApiVersion("v1").build();
            SubjectAccessReviewResponse response = osClient.subjectAccessReviews().create(request);
            if (log.isDebugEnabled())
                log.debug("isAdminResponse: " + response);
            userInfo.isClusterAdmin = response.getAllowed();
            userInfo.username = user.getMetadata().getName();
        } catch (Exception e) {
            log.error("Exception determining user's info. ", e);
        } finally {
            if (osClient != null)
                osClient.close();
        }
        return userInfo;
    }

    protected class UserInfo {
        String username;
        boolean isClusterAdmin;
    }

}