Package org.fcrepo.server.security.jaas

Source Code of org.fcrepo.server.security.jaas.AuthFilterJAAS

/*
* File: AuthFilterJAAS.java
*
* Copyright 2009 Muradora
*
* 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 org.fcrepo.server.security.jaas;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.security.Principal;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
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.HttpServletResponse;

import org.fcrepo.common.Constants;
import org.fcrepo.common.http.FilterConfigBean;
import org.fcrepo.server.security.jaas.auth.AuthHttpServletRequestWrapper;
import org.fcrepo.server.security.jaas.auth.handler.UsernamePasswordCallbackHandler;
import org.fcrepo.server.security.jaas.util.Base64;
import org.fcrepo.server.security.jaas.util.SubjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* A Servlet Filter for protecting resources. This filter uses JAAS for
* performing user authentication. Once a user is authenticated, a user
* principal object that is returned from the JAAS login module is created and
* added to the servlet request. The parameters of this filter are as follows:
* <ul>
* <li>
* <p>
* <strong>jaas.config.location</strong>
* </p>
* <p>
* This specifies the location of the jaas configuration file. The default is
* $FEDORA_HOME/server/config/jaas.conf
* </p>
* </li>
* <li>
* <p>
* <strong>jaas.config.name</strong>
* </p>
* <p>
* The name of the jaas configuration to use. The default is fedora-auth
* </p>
* </li>
* </ul>
*
* @author nish.naidoo@gmail.com
*/
public class AuthFilterJAAS
        implements Filter {

    private static final Logger logger = LoggerFactory
            .getLogger(AuthFilterJAAS.class);

    private static final String SESSION_SUBJECT_KEY =
            "javax.security.auth.subject";

    private static final String JAAS_CONFIG_KEY =
            "java.security.auth.login.config";

    private static final String JAAS_CONFIG_DEFAULT = "fedora-auth";

    private static final String ROLE_KEY = "role";

    private static final String FEDORA_ROLE_KEY = "fedoraRole";

    private static final String FEDORA_ATTRIBUTES_KEY =
            "FEDORA_AUX_SUBJECT_ATTRIBUTES";

    private String jaasConfigName = null;

    private final FilterConfigBean filterConfigBean = new FilterConfigBean();

    private FilterConfig filterConfig = filterConfigBean;

    private Set<String> userClassNames = null;

    private Set<String> roleClassNames = null;

    private Set<String> roleAttributeNames = null;

    private boolean authnAPIA = true;

    public void setUserClassNames(String names) {
        filterConfigBean.addInitParameter("userClassNames", names);
    }

    public void setAuthnAPIA(String a) {
        filterConfigBean.addInitParameter("authnAPIA", a);
    }

    public void setJaasConfigLocation(String location) {
        filterConfigBean.addInitParameter("jaas.config.location", location);
    }

    public void setJaasConfigName(String name) {
        filterConfigBean.addInitParameter("jaas.config.name", name);
    }

    public void setRoleClassNames(String names) {
        filterConfigBean.addInitParameter("roleClassNames", names);
    }

    public void setRoleAttributeNames(String names) {
        filterConfigBean.addInitParameter("roleAttributeNames", names);
    }

    @Override
    public void init(FilterConfig config) throws ServletException {
        this.filterConfig = config;
        if (this.filterConfig == null) {
            logger.info("No configuration for: " + this.getClass().getName());
        }

        init();
    }

    public void init() throws ServletException {
        // get FEDORA_HOME. This being set is mandatory.
        String fedoraHome = Constants.FEDORA_HOME;
        if (fedoraHome == null || "".equals(fedoraHome)) {
            String msg = "FEDORA_HOME environment variable not set";
            throw new ServletException(msg);
        }

        logger.info("using FEDORA_HOME: " + fedoraHome);

        // Get the jaas.conf file to use and the config to use from the
        // jaas.conf file. This defaults to $FEDORA_HOME/server/config/jaas.conf
        // and 'fedora-auth' for the configuration.
        String jaasConfigLocation = fedoraHome + "/server/config/jaas.conf";
        jaasConfigName = JAAS_CONFIG_DEFAULT;

        String tmp = null;

        tmp = filterConfig.getInitParameter("jaas.config.location");
        if (tmp != null && !"".equals(tmp)) {
            jaasConfigLocation = tmp;
            if (logger.isDebugEnabled()) {
                logger.debug("using location from init file: "
                        + jaasConfigLocation);
            }
        }

        tmp = filterConfig.getInitParameter("jaas.config.name");
        if (tmp != null && !"".equals(tmp)) {
            jaasConfigName = tmp;
            if (logger.isDebugEnabled()) {
                logger.debug("using name from init file: " + jaasConfigName);
            }
        }

        tmp = filterConfig.getInitParameter("authnAPIA");
        authnAPIA = Boolean.parseBoolean(tmp);

        tmp = filterConfig.getInitParameter("userClassNames");
        userClassNames = new HashSet<String>();
        if (tmp != null) {
            String[] names = tmp.split(" *, *");
            if (names != null && names.length > 0) {
                for (String n : names) {
                    userClassNames.add(n);
                }
            }
        }

        tmp = filterConfig.getInitParameter("roleClassNames");
        roleClassNames = new HashSet<String>();
        if (tmp != null) {
            String[] names = tmp.split(" *, *");
            if (names != null && names.length > 0) {
                for (String n : names) {
                    roleClassNames.add(n);
                }
            }
        }

        tmp = filterConfig.getInitParameter("roleAttributeNames");
        roleAttributeNames = new HashSet<String>();
        roleAttributeNames.add(ROLE_KEY);
        roleAttributeNames.add(FEDORA_ROLE_KEY);
        if (tmp != null) {
            String[] names = tmp.split(" *, *");
            if (names != null && names.length > 0) {
                for (String n : names) {
                    roleAttributeNames.add(n);
                }
            }
        }

        File jaasConfig = new File(jaasConfigLocation);
        if (!jaasConfig.exists()) {
            String msg =
                    "JAAS config file not at: " + jaasConfig.getAbsolutePath();
            logger.error(msg);
            throw new ServletException(msg);
        }

        System.setProperty(JAAS_CONFIG_KEY, jaasConfig.getAbsolutePath());

        logger.info("initialised servlet filter: " + this.getClass().getName());
    }

    /*
     * (non-Javadoc)
     * @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;
        HttpServletResponse res = (HttpServletResponse) response;

        // This is a hack to skip challenge authentication of API-A methods via the
        // REST API if API-A AuthN is off (as indicated by the "authnAPIA"
        // filter init-param).
        // If API-A AuthN is off, for all GET requests via the REST API,
        // except those which are known to be part of API-M, don't challenge for authentication
        // (but do pick up any preemptive credentials that are supplied)
        // FIXME: As other servlets that require authn also go through this filter,
        // there's probably a neater way to ensure we are only catching API-A methods
        // currently we are explicitly testing for other management URLs/paths
        // (probably should test fullPath for {appcontext}/objects (REST API) and then
        // test path for API-A methods, maybe regex to make it explicit)
        boolean doChallenge = true;
        if (!authnAPIA) {
            if (req.getMethod().equals("GET")) {
                String requestPath = req.getPathInfo();
                if (requestPath == null) requestPath = ""; // null is returned eg for /fedora/objects? - API-A, so we still want to do the below...
                String fullPath = req.getRequestURI();
                // API-M methods
                // potentially extra String evals, but aiming for clarity
                boolean isExport = requestPath.endsWith("/export");
                boolean isObjectXML = requestPath.endsWith("/objectXML");
                boolean isGetDatastream =
                        requestPath.contains("/datastreams/")
                                && !requestPath.endsWith("/content");
                isGetDatastream = isGetDatastream || (requestPath.endsWith("/datastreams")
                                && Boolean.valueOf(request.getParameter("profiles")));
                boolean isGetRelationships =
                        requestPath.endsWith("/relationships");
                boolean isValidate = requestPath.endsWith("/validate");
                // management get methods (LITE API, control)
                boolean isManagement =
                        fullPath.endsWith("/management/control")
                                || fullPath.endsWith("/management/getNextPID");
                // user servlet
                boolean isUserServlet = fullPath.endsWith("/user");
                // challenge if API-M or one of the above other services (otherwise we assume it is API-A)
                doChallenge =
                        isExport || isObjectXML || isGetDatastream
                                || isGetRelationships || isValidate
                                || isManagement || isUserServlet;
            }
        }

        if (logger.isDebugEnabled()) {
            logger.debug("incoming filter: " + this.getClass().getName());
            logger.debug("session-id: " + req.getSession().getId());
        }

        Subject subject = authenticate(req);

        if (subject == null) {
            if (doChallenge) {
                loginForm(res);
                return;
            } else {
                // no auth required, and none supplied, do rest of chain
                chain.doFilter(request, response);
                return;
            }
        }

        // obtain the user principal from the subject and add to servlet.
        Principal userPrincipal = getUserPrincipal(subject);

        // obtain the user roles from the subject and add to servlet.
        Set<String> userRoles = getUserRoles(subject);

        // wrap the request in one that has the ability to store role
        // and principal information and store this information.
        AuthHttpServletRequestWrapper authRequest =
                new AuthHttpServletRequestWrapper(req);
        authRequest.setUserPrincipal(userPrincipal);
        authRequest.setUserRoles(userRoles);

        // add the roles that were obtained to the Subject.
        addRolesToSubject(subject, userRoles);

        // populate FEDORA_AUX_SUBJECT_ATTRIBUTES with fedoraRole
        // and any additional Subject attributes
        populateFedoraAttributes(subject, userRoles, authRequest);

        chain.doFilter(authRequest, response);

        if (logger.isDebugEnabled()) {
            logger.debug("outgoing filter: " + this.getClass().getName());
        }
    }

    @Override
    public void destroy() {
        logger.info("destroying servlet filter: " + this.getClass().getName());
        filterConfig = null;
    }

    /**
     * Sends a 401 error to the browser. This forces a login box to be displayed
     * allowing the user to login.
     *
     * @param response
     *        the response to set the headers and status
     */
    private void loginForm(HttpServletResponse response) throws IOException {
        response.reset();
        response.addHeader("WWW-Authenticate",
                           "Basic realm=\"!!Fedora Repository Server\"");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        OutputStream out = response.getOutputStream();
        out.write("Fedora: 401 ".getBytes());
        out.flush();
        out.close();
    }

    /**
     * Performs the authentication. Once a Subject is obtained, it is stored in
     * the users session. Subsequent requests check for the existence of this
     * object before performing the authentication again.
     *
     * @param req
     *        the servlet request.
     * @return a user principal that was extracted from the login context.
     */
    private Subject authenticate(HttpServletRequest req) {
        String authorization = req.getHeader("authorization");
        if (authorization == null || "".equals(authorization.trim())) {
            return null;
        }

        // subject from session instead of re-authenticating
        // can't change username/password for this session.
        Subject subject =
                (Subject) req.getSession().getAttribute(authorization);
        if (subject != null) {
            return subject;
        }

        String auth = null;
        try {
            byte[] data = Base64.decode(authorization.substring(6));
            auth = new String(data);
        } catch (IOException e) {
            logger.error(e.toString());
            return null;
        }

        String username = auth.substring(0, auth.indexOf(':'));
        String password = auth.substring(auth.indexOf(':') + 1);

        if (logger.isDebugEnabled()) {
            logger.debug("auth username: " + username);
        }

        LoginContext loginContext = null;
        try {
            CallbackHandler handler =
                    new UsernamePasswordCallbackHandler(username, password);
            loginContext = new LoginContext(jaasConfigName, handler);
            loginContext.login();
        } catch (LoginException le) {
            logger.error(le.toString());
            return null;
        }

        // successfully logged in
        subject = loginContext.getSubject();

        // object accessable by a fixed key for usage
        req.getSession().setAttribute(SESSION_SUBJECT_KEY, subject);

        // object accessable only by base64 encoded username:password that was
        // initially used - prevents some dodgy stuff
        req.getSession().setAttribute(authorization, subject);

        return subject;
    }

    /**
     * Given a subject, obtain the userPrincipal from it. The user principal is
     * defined by a Principal class that can be defined in the web.xml file. If
     * this is undefined, the first principal found is assumed to be the
     * userPrincipal.
     *
     * @param subject
     *        the subject returned from authentication.
     * @return the userPrincipal associated with the given subject.
     */
    private Principal getUserPrincipal(Subject subject) {
        Principal userPrincipal = null;

        Set<Principal> principals = subject.getPrincipals();

        // try and get userPrincipal based on userClassNames
        if (userClassNames != null && userClassNames.size() > 0) {
            for (Principal p : principals) {
                if (userPrincipal == null
                        && userClassNames.contains(p.getClass().getName())) {
                    userPrincipal = p;
                }
            }
        }

        // no userPrincipal found using userClassNames, just grab first principal
        if (userPrincipal == null) {
            Iterator<Principal> i = principals.iterator();
            // should always have 1 at least and 1st should be user principal
            if (i.hasNext()) {
                userPrincipal = i.next();
            }
        }

        if (logger.isDebugEnabled()) {
            logger.debug("found userPrincipal ["
                    + userPrincipal.getClass().getName() + "]: "
                    + userPrincipal.getName());
        }

        return userPrincipal;
    }

    /**
     * Obtains the roles for the user based on the class names and attribute
     * names provided in the web.xml file.
     *
     * @param subject
     *        the subject returned from authentication.
     * @return a set of strings that represent the users roles.
     */
    private Set<String> getUserRoles(Subject subject) {
        Set<String> userRoles = new HashSet<String>();

        // get roles from specified classes
        Set<Principal> principals = subject.getPrincipals();
        if (roleClassNames != null && roleClassNames.size() > 0) {
            for (Principal p : principals) {
                if (roleClassNames.contains(p.getClass().getName())) {
                    userRoles.add(p.getName());
                }
            }
        }

        // get roles from specified attributes
        Map<String, Set<String>> attributes =
                SubjectUtils.getAttributes(subject);
        if (attributes != null) {
            for (String key : attributes.keySet()) {
                if (roleAttributeNames.contains(key)) {
                    userRoles.addAll(attributes.get(key));
                }
            }
        }

        if (logger.isDebugEnabled()) {
            for (String r : userRoles) {
                logger.debug("found role: " + r);
            }
        }

        return userRoles;
    }

    /**
     * Adds roles to the Subject object.
     *
     * @param subject
     *        the subject that was returned from authentication.
     * @param userRoles
     *        the set of user roles that were found.
     */
    private void addRolesToSubject(Subject subject, Set<String> userRoles) {
        if (userRoles == null) {
            userRoles = new HashSet<String>();
        }

        Map<String, Set<String>> attributes =
                SubjectUtils.getAttributes(subject);

        Set<String> roles = attributes.get(ROLE_KEY);
        if (roles == null) {
            roles = new HashSet<String>();
            attributes.put(ROLE_KEY, roles);
        }

        for (String role : userRoles) {
            roles.add(role);
            if (logger.isDebugEnabled()) {
                logger.debug("added role: " + role);
            }
        }
    }

    /**
     * Add roles and other subject attributes where Fedora expects them - a Map
     * called FEDORA_AUX_SUBJECT_ATTRIBUTES. Roles will be put in "fedoraRole"
     * and others will be named as-is.
     *
     * @param subject
     *        the subject from authentication.
     * @param userRoles
     *        the set of user roles.
     * @param request
     *        the request in which to place the attributes.
     */
    private void populateFedoraAttributes(Subject subject,
                                          Set<String> userRoles,
                                          HttpServletRequest request) {
        Map<String, Set<String>> attributes =
                SubjectUtils.getAttributes(subject);
        if (attributes == null) {
            attributes = new HashMap<String, Set<String>>();
        }

        // get the fedoraRole attribute or create it.
        Set<String> roles = attributes.get(FEDORA_ROLE_KEY);
        if (roles == null) {
            roles = new HashSet<String>();
            attributes.put(FEDORA_ROLE_KEY, roles);
        }

        roles.addAll(userRoles);

        request.setAttribute(FEDORA_ATTRIBUTES_KEY, attributes);
    }
}
TOP

Related Classes of org.fcrepo.server.security.jaas.AuthFilterJAAS

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.