All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.catalina.realm.RealmBase Maven / Gradle / Ivy

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2016 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 *
 *
 * This file incorporates work covered by the following copyright and
 * permission notice:
 *
 * Copyright 2004 The Apache Software Foundation
 *
 * 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.
 */
// Portions Copyright [2017-2019] [Payara Foundation and/or its affiliates]
package org.apache.catalina.realm;

import static java.util.logging.Level.FINE;
import static org.apache.catalina.connector.Response.HTTP_RESPONSE_DATE_HEADER;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.nio.charset.CharacterCodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.management.ObjectName;
import javax.servlet.ServletContext;
// END SJSWS 6324431
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.catalina.Authenticator;
import org.apache.catalina.Container;
import org.apache.catalina.Context;
import org.apache.catalina.Globals;
import org.apache.catalina.HttpRequest;
import org.apache.catalina.HttpResponse;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.LogFacade;
import org.apache.catalina.Realm;
import org.apache.catalina.authenticator.AuthenticatorBase;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.deploy.LoginConfig;
import org.apache.catalina.deploy.SecurityCollection;
import org.apache.catalina.deploy.SecurityConstraint;
import org.apache.catalina.util.HexUtils;
import org.apache.catalina.util.LifecycleSupport;
import org.apache.catalina.util.MD5Encoder;

import com.sun.enterprise.util.Utility;

/**
 * Simple implementation of Realm that reads an XML file to configure the valid users, passwords, and roles. The
 * file format (and default file location) are identical to those currently supported by Tomcat 3.X.
 *
 * @author Craig R. McClanahan
 * @version $Revision: 1.14 $ $Date: 2007/04/18 17:27:23 $
 */

public abstract class RealmBase implements Lifecycle, Realm {

    protected static final Logger log = LogFacade.getLogger();
    protected static final ResourceBundle rb = log.getResourceBundle();

    // START SJSAS 6202703
    /**
     * "Expires" header always set to Date(1), so generate once only
     */
    private static final String DATE_ONE = (new SimpleDateFormat(HTTP_RESPONSE_DATE_HEADER, Locale.US)).format(new Date(1));
    // END SJSAS 6202703

    // ----------------------------------------------------- Instance Variables

    /**
     * The debugging detail level for this component.
     */
    protected int debug = 0;

    /**
     * The Container with which this Realm is associated.
     */
    protected Container container;

    /**
     * Flag indicating whether a check to see if the request is secure is required before adding Pragma and Cache-Control
     * headers when proxy caching has been disabled
     */
    protected boolean checkIfRequestIsSecure;

    /**
     * Digest algorithm used in storing passwords in a non-plaintext format. Valid values are those accepted for the
     * algorithm name by the MessageDigest class, or null if no digesting should be performed.
     */
    protected String digest;

    /**
     * The encoding charset for the digest.
     */
    protected String digestEncoding;

    /**
     * Descriptive information about this Realm implementation.
     */
    protected static final String info = "org.apache.catalina.realm.RealmBase/1.0";

    /**
     * The lifecycle event support for this component.
     */
    protected LifecycleSupport lifecycle = new LifecycleSupport(this);

    /**
     * The MessageDigest object for digesting user credentials (passwords).
     */
    protected volatile MessageDigest md;

    /**
     * The MD5 helper object for this class.
     */
    protected static final MD5Encoder md5Encoder = new MD5Encoder();

    /**
     * MD5 message digest provider.
     */
    protected static volatile MessageDigest md5Helper;

    /**
     * Has this component been started?
     */
    protected boolean started;

    /**
     * The property change support for this component.
     */
    protected PropertyChangeSupport support = new PropertyChangeSupport(this);

    /**
     * Should we validate client certificate chains when they are presented?
     */
    protected boolean validate = true;
    

    // ------------------------------------------------------------- Properties

    /**
     * Return the Container with which this Realm has been associated.
     */
    @Override
    public Container getContainer() {
        return container;
    }

    /**
     * Return the debugging detail level for this component.
     */
    public int getDebug() {
        return debug;
    }

    /**
     * Set the debugging detail level for this component.
     *
     * @param debug The new debugging detail level
     */
    public void setDebug(int debug) {
        this.debug = debug;
    }

    /**
     * Set the Container with which this Realm has been associated.
     *
     * @param container The associated Container
     */
    @Override
    public void setContainer(Container container) {
        Container oldContainer = this.container;
        this.container = container;
        this.checkIfRequestIsSecure = container.isCheckIfRequestIsSecure();
        
        support.firePropertyChange("container", oldContainer, this.container);
    }

    /**
     * Return the digest algorithm used for storing credentials.
     */
    public String getDigest() {
        return digest;
    }

    /**
     * Set the digest algorithm used for storing credentials.
     *
     * @param digest The new digest algorithm
     */
    public void setDigest(String digest) {
        this.digest = digest;
    }

    /**
     * Returns the digest encoding charset.
     *
     * @return The charset (may be null) for platform default
     */
    public String getDigestEncoding() {
        return digestEncoding;
    }

    /**
     * Sets the digest encoding charset.
     *
     * @param charset The charset (null for platform default)
     */
    public void setDigestEncoding(String charset) {
        digestEncoding = charset;
    }

    /**
     * Return descriptive information about this Realm implementation and the corresponding version number, in the format
     * <description>/<version>.
     */
    @Override
    public String getInfo() {
        return info;
    }

    /**
     * Return the "validate certificate chains" flag.
     */
    public boolean getValidate() {
        return validate;
    }

    /**
     * Set the "validate certificate chains" flag.
     *
     * @param validate The new validate certificate chains flag
     */
    public void setValidate(boolean validate) {
        this.validate = validate;
    }

    // --------------------------------------------------------- Public Methods

    /**
     * Add a property change listener to this component.
     *
     * @param listener The listener to add
     */
    @Override
    public void addPropertyChangeListener(PropertyChangeListener listener) {
        support.addPropertyChangeListener(listener);
    }

    /**
     * Return the Principal associated with the specified username and credentials, if there is one; otherwise return
     * null.
     *
     * @param username Username of the Principal to look up
     * @param credentials Password or other credentials to use in authenticating this username
     */
    @Override
    public Principal authenticate(String username, char[] credentials) {
        char[] serverCredentials = getPassword(username);

        boolean validated;
        if (serverCredentials == null) {
            validated = false;
        } else if (hasMessageDigest()) {
            validated = equalsIgnoreCase(serverCredentials, digest(credentials));
        } else {
            validated = Arrays.equals(serverCredentials, credentials);
        }
        
        if (!validated) {
            return null;
        }
        
        return getPrincipal(username);
    }

    /**
     * Return the Principal associated with the specified username, which matches the digest calculated using the given
     * parameters using the method described in RFC 2069; otherwise return null.
     *
     * @param username Username of the Principal to look up
     * @param clientDigest Digest which has been submitted by the client
     * @param nOnce Unique (or supposedly unique) token which has been used for this request
     * @param realm Realm name
     * @param md5a2 Second MD5 digest used to calculate the digest : MD5(Method + ":" + uri)
     */
    @Override
    public Principal authenticate(String username, char[] clientDigest, String nOnce, String nc, String cnonce, String qop, String realm,
            char[] md5a2) {

        char[] md5a1 = getDigest(username, realm);
        if (md5a1 == null)
            return null;

        int nOnceLength = ((nOnce != null) ? nOnce.length() : 0);
        int ncLength = ((nc != null) ? nc.length() : 0);
        int cnonceLength = ((cnonce != null) ? cnonce.length() : 0);
        int qopLength = ((qop != null) ? qop.length() : 0);
        int md5a2Length = ((md5a2 != null) ? md5a2.length : 0);

        // serverDigestValue = md5a1:nOnce:nc:cnonce:qop:md5a2
        char[] serverDigestValue = new char[md5a1.length + 1 + nOnceLength + 1 + ncLength + 1 + cnonceLength + 1 + qopLength + 1
                + md5a2Length];

        System.arraycopy(md5a1, 0, serverDigestValue, 0, md5a1.length);
        int ind = md5a1.length;
        serverDigestValue[ind++] = ':';
        if (nOnce != null) {
            System.arraycopy(nOnce.toCharArray(), 0, serverDigestValue, ind, nOnceLength);
            ind += nOnceLength;
        }
        serverDigestValue[ind++] = ':';
        if (nc != null) {
            System.arraycopy(nc.toCharArray(), 0, serverDigestValue, ind, ncLength);
            ind += ncLength;
        }
        serverDigestValue[ind++] = ':';
        if (cnonce != null) {
            System.arraycopy(cnonce.toCharArray(), 0, serverDigestValue, ind, cnonceLength);
            ind += cnonceLength;
        }
        serverDigestValue[ind++] = ':';
        if (qop != null) {
            System.arraycopy(qop.toCharArray(), 0, serverDigestValue, ind, qopLength);
            ind += qopLength;
        }
        serverDigestValue[ind++] = ':';
        if (md5a2 != null) {
            System.arraycopy(md5a2, 0, serverDigestValue, ind, md5a2Length);
        }

        byte[] valueBytes = null;

        try {
            valueBytes = Utility.convertCharArrayToByteArray(serverDigestValue, getDigestEncoding());
        } catch (CharacterCodingException cce) {
            String msg = MessageFormat.format(rb.getString(LogFacade.ILLEGAL_DIGEST_ENCODING_EXCEPTION), getDigestEncoding());
            log.log(Level.SEVERE, msg, cce);
            throw new IllegalArgumentException(cce.getMessage());
        }

        char[] serverDigest = null;
        // Bugzilla 32137
        synchronized (md5Helper) {
            serverDigest = md5Encoder.encode(md5Helper.digest(valueBytes));
        }

        if (log.isLoggable(Level.FINE)) {
            String msg = "Username:" + username + " ClientSigest:" + Arrays.toString(clientDigest) + " nOnce:" + nOnce + " nc:" + nc
                    + " cnonce:" + cnonce + " qop:" + qop + " realm:" + realm + "md5a2:" + Arrays.toString(md5a2) + " Server digest:"
                    + String.valueOf(serverDigest);
            log.log(Level.FINE, msg);
        }

        if (Arrays.equals(serverDigest, clientDigest)) {
            return getPrincipal(username);
        } else {
            return null;
        }
    }

    /**
     * Return the Principal associated with the specified chain of X509 client certificates. If there is none, return
     * null.
     *
     * @param certificates Array of client certificates, with the first one in the array being the certificate of the client
     * itself.
     */
    @Override
    public Principal authenticate(X509Certificate certificates[]) {
        if (certificates == null || certificates.length < 1) {
            return null;
        }

        // Check the validity of each certificate in the chain
        if (log.isLoggable(FINE)) {
            log.log(FINE, "Authenticating client certificate chain");
        }
        
        if (validate) {
            for (X509Certificate certificate : certificates) {
                if (log.isLoggable(FINE)) {
                    log.log(FINE, "Checking validity for ''{0}''", certificate.getSubjectX500Principal().getName());
                }
                try {
                    certificate.checkValidity();
                }catch (Exception e) {
                    log.log(FINE, "Validity exception", e);
                    return (null);
                }
            }
        }

        // Check the existence of the client Principal in our database
        return getPrincipal(certificates[0].getSubjectX500Principal().getName());

    }

    /**
     * Execute a periodic task, such as reloading, etc. This method will be invoked inside the classloading context of this
     * container. Unexpected throwables will be caught and logged.
     */
    public void backgroundProcess() {
    }

    /**
     * Return the SecurityConstraints configured to guard the request URI for this request, or null if there is
     * no such constraint.
     *
     * @param request Request we are processing
     * @param context Context the Request is mapped to
     */
    @Override
    public SecurityConstraint[] findSecurityConstraints(HttpRequest request, Context context) {
        return findSecurityConstraints(request.getRequestPathMB().toString(), ((HttpServletRequest) request.getRequest()).getMethod(),
                context);
    }

    /**
     * Gets the security constraints configured by the given context for the given request URI and method.
     *
     * @param uri the request URI (minus the context Path)
     * @param method the request method
     * @param context the context
     *
     * @return the security constraints configured by the given context for the given request URI and method, or null
     */
    @Override
    public SecurityConstraint[] findSecurityConstraints(String uri, String method, Context context) {

        ArrayList results = null;

        // Are there any defined security constraints?
        if (!context.hasConstraints()) {
            if (log.isLoggable(Level.FINE))
                log.log(Level.FINE, "  No applicable constraints defined");
            return (null);
        }

        // START SJSWS 6324431
        String origUri = uri;
        boolean caseSensitiveMapping = ((StandardContext) context).isCaseSensitiveMapping();
        if (uri != null && !caseSensitiveMapping) {
            uri = uri.toLowerCase(Locale.ENGLISH);
        }
        // END SJSWS 6324431

        boolean found = false;

        List constraints = context.getConstraints();
        Iterator i = constraints.iterator();
        while (i.hasNext()) {
            SecurityConstraint constraint = i.next();
            SecurityCollection[] collection = constraint.findCollections();

            // If collection is null, continue to avoid an NPE
            // See Bugzilla 30624
            if (collection == null) {
                continue;
            }

            if (log.isLoggable(Level.FINEST)) {
                /*
                 * SJSWS 6324431 log.trace("  Checking constraint '" + constraints[i] + "' against " + method + " " + uri + " --> " +
                 * constraints[i].included(uri, method));
                 */
                // START SJSWS 6324431
                String msg = "Checking constraint '" + constraint + "' against " + method + " " + origUri + " --> "
                        + constraint.included(uri, method, caseSensitiveMapping);
                log.log(Level.FINEST, msg);
                // END SJSWS 6324431
            }
            /*
             * SJSWS 6324431 if (log.isDebugEnabled() && constraints[i].included( uri, method)) { log.debug("  Matched constraint '"
             * + constraints[i] + "' against " + method + " " + uri); }
             */
            // START SJSWS 6324431
            if (log.isLoggable(Level.FINE) && constraint.included(uri, method, caseSensitiveMapping)) {

                log.log(Level.FINE, "  Matched constraint ''{0}'' against {1} {2}", new Object[]{constraint, method, origUri});

            }
            // END SJSWS 6324431
            for (SecurityCollection collection1 : collection) {
                String[] patterns = collection1.findPatterns();
                // If patterns is null, continue to avoid an NPE
                // See Bugzilla 30624
                if (patterns == null) {
                    continue;
                }
                for (int k = 0; k < patterns.length; k++) {
                    /*
                    * SJSWS 6324431 if(uri.equals(patterns[k])) {
                    */
                    // START SJSWS 6324431
                    String pattern = caseSensitiveMapping ? patterns[k] : patterns[k].toLowerCase(Locale.ENGLISH);
                    if (uri != null && uri.equals(pattern)) {
                        // END SJSWS 6324431
                        found = true;
                        if (collection1.findMethod(method)) {
                            if (results == null) {
                                results = new ArrayList();
                            }
                            results.add(constraint);
                        }
                    }
                }
            }
        } // while

        if (found) {
            return resultsToArray(results);
        }

        int longest = -1;

        i = constraints.iterator();
        while (i.hasNext()) {
            SecurityConstraint constraint = i.next();
            SecurityCollection[] collection = constraint.findCollections();

            // If collection is null, continue to avoid an NPE
            // See Bugzilla 30624
            if (collection == null) {
                continue;
            }

            if (log.isLoggable(Level.FINEST)) {
                /*
                 * SJSWS 6324431 log.trace("  Checking constraint '" + constraints[i] + "' against " + method + " " + uri + " --> " +
                 * constraints[i].included(uri, method));
                 */
                // START SJSWS 6324431
                String msg = "  Checking constraint '" + constraint + "' against " + method + " " + origUri + " --> "
                        + constraint.included(uri, method, caseSensitiveMapping);
                log.log(Level.FINE, msg);
                // END SJSWS 6324431
            }
            /*
             * SJSWS 6324431 if (log.isDebugEnabled() && constraints[i].included( uri, method)) { log.debug("  Matched constraint '"
             * + constraints[i] + "' against " + method + " " + uri); }
             */
            // START SJSWS 6324431
            if (log.isLoggable(Level.FINE) && constraint.included(uri, method, caseSensitiveMapping)) {
                log.log(Level.FINE, "  Matched constraint ''{0}'' against {1} {2}", new Object[]{constraint, method, origUri});
            }
            // END SJSWS 6324431

            for (int j = 0; j < collection.length; j++) {
                String[] patterns = collection[j].findPatterns();
                // If patterns is null, continue to avoid an NPE
                // See Bugzilla 30624
                if (patterns == null) {
                    continue;
                }

                boolean matched = false;
                int length = -1;
                for (String pattern1 : patterns) {
                    /*
                     * SJSWS 6324431 String pattern = patterns[k];
                     */
                    // START SJSWS 6324431
                    String pattern = caseSensitiveMapping ? pattern1 : pattern1.toLowerCase(Locale.ENGLISH);
                    // END SJSWS 6324431
                    if (pattern.startsWith("/") && pattern.endsWith("/*") && pattern.length() >= longest) {

                        if (pattern.length() == 2) {
                            matched = true;
                            length = pattern.length();
                        } else if (uri != null && (pattern.regionMatches(0, uri, 0, pattern.length() - 1)
                                || (pattern.length() - 2 == uri.length() && pattern.regionMatches(0, uri, 0, pattern.length() - 2)))) {
                            matched = true;
                            length = pattern.length();
                        }
                    }
                }
                if (matched) {
                    found = true;
                    if (length > longest) {
                        if (results != null) {
                            results.clear();
                        }
                        longest = length;
                    }
                    if (collection[j].findMethod(method)) {
                        if (results == null) {
                            results = new ArrayList();
                        }
                        results.add(constraint);
                    }
                }
            }
        } // while

        if (found) {
            return resultsToArray(results);
        }

        i = constraints.iterator();
        while (i.hasNext()) {
            SecurityConstraint constraint = i.next();
            SecurityCollection[] collection = constraint.findCollections();

            // If collection is null, continue to avoid an NPE
            // See Bugzilla 30624
            if (collection == null) {
                continue;
            }

            if (log.isLoggable(Level.FINEST)) {
                /*
                 * SJSWS 6324431 log.trace("  Checking constraint '" + constraints[i] + "' against " + method + " " + uri + " --> " +
                 * constraints[i].included(uri, method));
                 */
                // START SJSWS 6324431
                String msg = "  Checking constraint '" + constraint + "' against " + method + " " + origUri + " --> "
                        + constraint.included(uri, method, caseSensitiveMapping);
                log.log(Level.FINEST, msg);
                // END SJSWS 6324431
            }
            /*
             * SJSWS 6324431 if (log.isDebugEnabled() && constraints[i].included( uri, method)) { log.debug("  Matched constraint '"
             * + constraints[i] + "' against " + method + " " + uri); }
             */
            // START SJSWS 6324431
            if (log.isLoggable(Level.FINE) && constraint.included(uri, method, caseSensitiveMapping)) {

                log.log(Level.FINE, "  Matched constraint ''{0}'' against {1} {2}", new Object[]{constraint, method, origUri});
            }
            // END SJSWS 6324431

            boolean matched = false;
            int pos = -1;
            for (int j = 0; j < collection.length; j++) {
                String[] patterns = collection[j].findPatterns();
                // If patterns is null, continue to avoid an NPE
                // See Bugzilla 30624
                if (patterns == null) {
                    continue;
                }

                for (int k = 0; k < patterns.length && !matched; k++) {
                    /*
                     * SJSWS 6324431 String pattern = patterns[k];
                     */
                    // START SJSWS 6324431
                    String pattern = caseSensitiveMapping ? patterns[k] : patterns[k].toLowerCase(Locale.ENGLISH);
                    // END SJSWS 6324431
                    if (uri != null && pattern.startsWith("*.")) {
                        int slash = uri.lastIndexOf('/');
                        int dot = uri.lastIndexOf('.');
                        if (slash >= 0 && dot > slash && dot != uri.length() - 1 && uri.length() - dot == pattern.length() - 1) {
                            if (pattern.regionMatches(1, uri, dot, uri.length() - dot)) {
                                matched = true;
                                pos = j;
                            }
                        }
                    }
                }
            }

            if (matched) {
                found = true;
                if (collection[pos].findMethod(method)) {
                    if (results == null) {
                        results = new ArrayList();
                    }
                    results.add(constraint);
                }
            }
        } // while

        if (found) {
            return resultsToArray(results);
        }

        i = constraints.iterator();
        while (i.hasNext()) {
            SecurityConstraint constraint = i.next();
            SecurityCollection[] collection = constraint.findCollections();

            // If collection is null, continue to avoid an NPE
            // See Bugzilla 30624
            if (collection == null) {
                continue;
            }

            if (log.isLoggable(Level.FINEST)) {
                /*
                 * SJSWS 6324431 log.trace("  Checking constraint '" + constraints[i] + "' against " + method + " " + uri + " --> " +
                 * constraints[i].included(uri, method));
                 */
                // START SJSWS 6324431
                String msg = "  Checking constraint '" + constraint + "' against " + method + " " + origUri + " --> "
                        + constraint.included(uri, method, caseSensitiveMapping);
                log.log(Level.FINEST, msg);
                // END SJSWS 6324431
            }
            /*
             * SJSWS 6324431 if (log.isDebugEnabled() && constraints[i].included( uri, method)) { log.debug("  Matched constraint '"
             * + constraints[i] + "' against " + method + " " + uri); }
             */
            // START SJSWS 6324431
            if (log.isLoggable(Level.FINE) && constraint.included(uri, method, caseSensitiveMapping)) {
                log.log(Level.FINE, "  Matched constraint ''{0}'' against {1} {2}", new Object[]{constraint, method, origUri});
            }
            // END SJSWS 6324431

            for (int j = 0; j < collection.length; j++) {
                String[] patterns = collection[j].findPatterns();

                // If patterns is null, continue to avoid an NPE
                // See Bugzilla 30624
                if (patterns == null) {
                    continue;
                }

                boolean matched = false;
                for (int k = 0; k < patterns.length && !matched; k++) {
                    /*
                     * SJSWS 6324431 String pattern = patterns[k];
                     */
                    // START SJSWS 6324431
                    String pattern = caseSensitiveMapping ? patterns[k] : patterns[k].toLowerCase(Locale.ENGLISH);
                    // END SJSWS 6324431
                    if (pattern.equals("/")) {
                        matched = true;
                    }
                }
                if (matched) {
                    if (results == null) {
                        results = new ArrayList();
                    }
                    results.add(constraint);
                }
            }
        } // while

        if (results == null) {
            // No applicable security constraint was found
            if (log.isLoggable(Level.FINE))
                log.log(Level.FINE, "  No applicable constraint located");
        }

        return resultsToArray(results);
    }

    /**
     * Convert an ArrayList to a SecurityContraint [].
     */
    private SecurityConstraint[] resultsToArray(ArrayList results) {
        if (results == null) {
            return null;
        }
        SecurityConstraint[] array = new SecurityConstraint[results.size()];
        results.toArray(array);
        return array;
    }

    /**
     * Perform access control based on the specified authorization constraint. Return true if this constraint
     * is satisfied and processing should continue, or false otherwise.
     *
     * @param request Request we are processing
     * @param response Response we are creating
     * @param constraints Security constraint we are enforcing
     * @param context The Context to which client of this class is attached.
     *
     * @exception IOException if an input/output error occurs
     */
    @Override
    public boolean hasResourcePermission(HttpRequest request, HttpResponse response, SecurityConstraint[] constraints, Context context)
            throws IOException {

        if (constraints == null || constraints.length == 0)
            return (true);

        // Which user principal have we already authenticated?
        Principal principal = ((HttpServletRequest) request.getRequest()).getUserPrincipal();
        for (int i = 0; i < constraints.length; i++) {
            SecurityConstraint constraint = constraints[i];
            String roles[] = constraint.findAuthRoles();
            if (roles == null)
                roles = new String[0];

            if (constraint.getAllRoles())
                return (true);

            if (log.isLoggable(Level.FINE))
                log.log(Level.FINE, "  Checking roles {0}", principal);

            if (roles.length == 0) {
                if (constraint.getAuthConstraint()) {

                    // BEGIN S1AS 4878272
                    ((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_FORBIDDEN);
                    response.setDetailMessage(rb.getString(LogFacade.ACCESS_RESOURCE_DENIED));
                    // END S1AS 4878272

                    if (log.isLoggable(Level.FINE))
                        log.log(Level.FINE, "No roles ");
                    return (false); // No listed roles means no access at all
                } else {
                    if (log.isLoggable(Level.FINE)) {
                        log.log(Level.FINE, "Passing all access");
                    }
                    return (true);
                }
            } else if (principal == null) {
                if (log.isLoggable(Level.FINE))
                    log.log(Level.FINE, "  No user authenticated, cannot grant access");

                // BEGIN S1AS 4878272
                ((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_FORBIDDEN);
                response.setDetailMessage(rb.getString(LogFacade.CONFIG_ERROR_NOT_AUTHENTICATED));
                // END S1AS 4878272
                return (false);
            }

            for (int j = 0; j < roles.length; j++) {
                if (hasRole(principal, roles[j])) {
                    if (log.isLoggable(Level.FINE))
                        log.log(Level.FINE, "Role found:  {0}", roles[j]);
                    return (true);
                } else {
                    if (log.isLoggable(Level.FINE))
                        log.log(Level.FINE, "No role found:  {0}", roles[j]);
                }
            }
        }
        // Return a "Forbidden" message denying access to this resource
        /*
         * S1AS 4878272 ((HttpServletResponse) response.getResponse()).sendError
         */
        // BEGIN S1AS 4878272
        ((HttpServletResponse) response.getResponse()).sendError(HttpServletResponse.SC_FORBIDDEN);
        response.setDetailMessage(rb.getString(LogFacade.ACCESS_RESOURCE_DENIED));
        // END S1AS 4878272
        return (false);

    }

    // START SJSAS 6232464
    /**
     * Return true if the specified Principal has the specified security role, within the context of this
     * Realm; otherwise return false. This method can be overridden by Realm implementations. The default
     * implementation is to forward to hasRole(Principal principal, String role).
     *
     * @param request Request we are processing
     * @param response Response we are creating
     * @param principal Principal for whom the role is to be checked
     * @param role Security role to be checked
     */
    @Override
    public boolean hasRole(HttpRequest request, HttpResponse response, Principal principal, String role) {
        return hasRole(principal, role);
    }
    // END SJSAS 6232464

    // START SJSAS 6202703
    /**
     * Checks whether or not authentication is needed. Returns an int, one of AUTHENTICATE_NOT_NEEDED, AUTHENTICATE_NEEDED,
     * or AUTHENTICATED_NOT_AUTHORIZED.
     *
     * @param request Request we are processing
     * @param response Response we are creating
     * @param constraints Security constraint we are enforcing
     * @param disableProxyCaching whether or not to disable proxy caching for protected resources.
     * @param securePagesWithPragma true if we add headers which are incompatible with downloading office documents in IE
     * under SSL but which fix a caching problem in Mozilla.
     * @param ssoEnabled true if sso is enabled
     * @exception IOException if an input/output error occurs
     */
    @Override
    public int preAuthenticateCheck(HttpRequest request, HttpResponse response, SecurityConstraint[] constraints,
            boolean disableProxyCaching, boolean securePagesWithPragma, boolean ssoEnabled) throws IOException {
        for (SecurityConstraint constraint : constraints) {
            if (constraint.getAuthConstraint()) {
                disableProxyCaching(request, response, disableProxyCaching, securePagesWithPragma);
                return Realm.AUTHENTICATE_NEEDED;
            }
        }
        return Realm.AUTHENTICATE_NOT_NEEDED;
    }

    /**
     * Authenticates the user making this request, based on the specified login configuration. Return true if
     * any specified requirements have been satisfied, or false if we have created a response challenge
     * already.
     *
     * @param request Request we are processing
     * @param response Response we are creating
     * @param context The Context to which client of this class is attached.
     * @param authenticator the current authenticator.
     * @exception IOException if an input/output error occurs
     */
    @Override
    public boolean invokeAuthenticateDelegate(HttpRequest request, HttpResponse response, Context context, Authenticator authenticator,
            boolean calledFromAuthenticate) throws IOException {
        LoginConfig config = context.getLoginConfig();
        return ((AuthenticatorBase) authenticator).authenticate(request, response, config);
    }

    /**
     * Post authentication for given request and response.
     *
     * @param request Request we are processing
     * @param response Response we are creating
     * @param context The Context to which client of this class is attached.
     * @exception IOException if an input/output error occurs
     */
    @Override
    public boolean invokePostAuthenticateDelegate(HttpRequest request, HttpResponse response, Context context) throws IOException {
        return true;
    }

    // END SJSAS 6202703

    /**
     * Return true if the specified Principal has the specified security role, within the context of this
     * Realm; otherwise return false. This method can be overridden by Realm implementations, but the default
     * is adequate when an instance of GenericPrincipal is used to represent authenticated Principals from this
     * Realm.
     *
     * @param principal Principal for whom the role is to be checked
     * @param role Security role to be checked
     */
    @Override
    public boolean hasRole(Principal principal, String role) {

        // Should be overridden in JAASRealm - to avoid pretty inefficient conversions
        if ((principal == null) || (role == null) || !(principal instanceof GenericPrincipal))
            return (false);

        GenericPrincipal gp = (GenericPrincipal) principal;
        if (!(gp.getRealm() == this)) {
            if (log.isLoggable(Level.FINE)) {
                log.log(Level.FINE, "Different realm {0} {1}", new Object[]{this, gp.getRealm()});
            }
        }
        boolean result = gp.hasRole(role);
        if (log.isLoggable(Level.FINE)) {
            String name = principal.getName();
            if (result) {
                log.log(Level.FINE, LogFacade.USERNAME_HAS_ROLE, new Object[] { name, role });
            } else {
                log.log(Level.FINE, LogFacade.USERNAME_NOT_HAVE_ROLE, new Object[] { name, role });
            }
        }
        return (result);

    }

    /**
     * Enforce any user data constraint required by the security constraint guarding this request URI.
     *
     * @param request Request we are processing
     * @param response Response we are creating
     * @param constraints Security constraint being checked
     *
     * @exception IOException if an input/output error occurs
     * 
     * @return true if this constraint was not violated and processing should continue, or false
     * if we have created a response already
     */
    @Override
    public boolean hasUserDataPermission(HttpRequest request, HttpResponse response, SecurityConstraint[] constraints) throws IOException {
        return hasUserDataPermission(request, response, constraints, null, null);
    }

    /**
     * Checks if the given request URI and method are the target of any user-data-constraint with a transport-guarantee of
     * CONFIDENTIAL, and whether any such constraint is already satisfied.
     * 
     * If uri and method are null, then the URI and method of the given request are checked.
     *
     * If a user-data-constraint exists that is not satisfied, then the given request will be redirected to HTTPS.
     *
     * @param request the request that may be redirected
     * @param response the response that may be redirected
     * @param constraints the security constraints to check against
     * @param uri the request URI (minus the context path) to check
     * @param method the request method to check
     *
     * @return true if the request URI and method are not the target of any unsatisfied user-data-constraint with a
     * transport-guarantee of CONFIDENTIAL, and false if they are (in which case the given request will have been redirected
     * to HTTPS)
     */
    @Override
    public boolean hasUserDataPermission(HttpRequest request, HttpResponse response, SecurityConstraint[] constraints, String uri,
            String method) throws IOException {
        // Is there a relevant user data constraint?
        if (constraints == null || constraints.length == 0) {
            if (log.isLoggable(Level.FINE))
                log.log(Level.FINE, "  No applicable security constraint defined");
            return (true);
        }

        for (int i = 0; i < constraints.length; i++) {
            SecurityConstraint constraint = constraints[i];
            String userConstraint = constraint.getUserConstraint();
            if (userConstraint == null) {
                if (log.isLoggable(Level.FINE))
                    log.log(Level.FINE, "  No applicable user data constraint defined");
                return (true);
            }
            if (userConstraint.equals(Constants.NONE_TRANSPORT)) {
                if (log.isLoggable(Level.FINE))
                    log.log(Level.FINE, "  User data constraint has no restrictions");
                return (true);
            }

        }

        // Validate the request against the user data constraint
        if (request.getRequest().isSecure()) {
            if (log.isLoggable(Level.FINE))
                log.log(Level.FINE, "  User data constraint already satisfied");
            return (true);
        }

        // Initialize variables we need to determine the appropriate action
        HttpServletRequest hrequest = (HttpServletRequest) request.getRequest();
        HttpServletResponse hresponse = (HttpServletResponse) response.getResponse();
        int redirectPort = request.getConnector().getRedirectPort();

        // Is redirecting disabled?
        if (redirectPort <= 0) {
            if (log.isLoggable(Level.FINE))
                log.log(Level.FINE, "  SSL redirect is disabled");
            /*
             * S1AS 4878272 hresponse.sendError response.sendError (HttpServletResponse.SC_FORBIDDEN, hrequest.getRequestURI());
             */
            // BEGIN S1AS 4878272
            hresponse.sendError(HttpServletResponse.SC_FORBIDDEN);
            response.setDetailMessage(hrequest.getRequestURI());
            // END S1AS 4878272
            return (false);
        }

        // Redirect to the corresponding SSL port
        StringBuilder file = new StringBuilder();
        String protocol = "https";
        String host = hrequest.getServerName();
        // Protocol
        file.append(protocol).append("://").append(host);
        // Host with port
        if (redirectPort != 443) {
            file.append(":").append(redirectPort);
        }
        // URI
        file.append(hrequest.getRequestURI());
        String requestedSessionId = hrequest.getRequestedSessionId();
        if ((requestedSessionId != null) && hrequest.isRequestedSessionIdFromURL()) {
            String sessionParameterName = ((request.getContext() != null) ? request.getContext().getSessionParameterName()
                    : Globals.SESSION_PARAMETER_NAME);
            file.append(";").append(sessionParameterName).append("=");
            file.append(requestedSessionId);
        }
        String queryString = hrequest.getQueryString();
        if (queryString != null) {
            file.append('?');
            file.append(queryString);
        }
        if (log.isLoggable(Level.FINE)) {
            log.log(Level.FINE, "Redirecting to {0}", file.toString());
        }
        hresponse.sendRedirect(file.toString());

        return (false);
    }

    /**
     * Remove a property change listener from this component.
     *
     * @param listener The listener to remove
     */
    @Override
    public void removePropertyChangeListener(PropertyChangeListener listener) {
        support.removePropertyChangeListener(listener);
    }

    // ------------------------------------------------------ Lifecycle Methods

    /**
     * Add a lifecycle event listener to this component.
     *
     * @param listener The listener to add
     */
    @Override
    public void addLifecycleListener(LifecycleListener listener) {
        lifecycle.addLifecycleListener(listener);
    }

    /**
     * Gets the (possibly empty) list of lifecycle listeners associated with this Realm.
     */
    @Override
    public List findLifecycleListeners() {
        return lifecycle.findLifecycleListeners();
    }

    /**
     * Remove a lifecycle event listener from this component.
     *
     * @param listener The listener to remove
     */
    @Override
    public void removeLifecycleListener(LifecycleListener listener) {
        lifecycle.removeLifecycleListener(listener);
    }

    /**
     * Prepare for the beginning of active use of the public methods of this component. This method should be called before
     * any of the public methods of this component are utilized. It should also send a LifecycleEvent of type START_EVENT to
     * any registered listeners.
     *
     * @exception LifecycleException if this component detects a fatal error that prevents this component from being used
     */
    @Override
    public void start() throws LifecycleException {

        // Validate and update our current component state
        if (started) {
            if (log.isLoggable(Level.INFO)) {
                log.log(Level.FINE, LogFacade.REALM_BEEN_STARTED);
            }
            return;
        }
        lifecycle.fireLifecycleEvent(START_EVENT, null);
        started = true;

        // Create a MessageDigest instance for credentials, if desired
        if (digest != null) {
            try {
                md = MessageDigest.getInstance(digest);
            } catch (NoSuchAlgorithmException e) {
                String msg = MessageFormat.format(rb.getString(LogFacade.INVALID_ALGORITHM_EXCEPTION), digest);
                throw new LifecycleException(msg, e);
            }
        }

    }

    /**
     * Gracefully terminate the active use of the public methods of this component. This method should be the last one
     * called on a given instance of this component. It should also send a LifecycleEvent of type STOP_EVENT to any
     * registered listeners.
     *
     * @exception LifecycleException if this component detects a fatal error that needs to be reported
     */
    @Override
    public void stop() throws LifecycleException {

        // Validate and update our current component state
        if (!started) {
            if (log.isLoggable(Level.INFO)) {
                log.log(Level.INFO, LogFacade.REALM_NOT_BEEN_STARTED);
            }
            return;
        }
        lifecycle.fireLifecycleEvent(STOP_EVENT, null);
        started = false;

        // Clean up allocated resources
        md = null;

        destroy();

    }

    public void destroy() {
        // no op
    }

    @Override
    public void logout(HttpRequest hreq) {
        // no-op
    }

    @Override
    public boolean isSecurityExtensionEnabled(ServletContext servletContext) {
        return false;
    }

    // ------------------------------------------------------ Protected Methods

    /**
     * Digest the password using the specified algorithm and convert the result to a corresponding hexadecimal string. If
     * exception, the plain credentials string is returned.
     *
     * @param credentials Password or other credentials to use in authenticating this username
     */
    protected char[] digest(char[] credentials) {

        // If no MessageDigest instance is specified, return unchanged
        if (hasMessageDigest() == false)
            return (credentials);

        // Digest the user credentials and return as hexadecimal
        synchronized (this) {
            try {
                md.reset();

                byte[] bytes = null;
                try {
                    bytes = Utility.convertCharArrayToByteArray(credentials, getDigestEncoding());
                } catch (CharacterCodingException cce) {
                    String msg = MessageFormat.format(rb.getString(LogFacade.ILLEGAL_DIGEST_ENCODING_EXCEPTION), getDigestEncoding());
                    log.log(Level.SEVERE, msg, cce);
                    throw new IllegalArgumentException(cce.getMessage());
                }
                md.update(bytes);

                return (HexUtils.convert(md.digest()));
            } catch (Exception e) {
                log.log(Level.SEVERE, LogFacade.ERROR_DIGESTING_USER_CREDENTIAL_EXCEPTION, e);
                return (credentials);
            }
        }

    }

    protected boolean hasMessageDigest() {
        return !(md == null);
    }

    /**
     * Return the digest associated with given principal's user name.
     */
    protected char[] getDigest(String username, String realmName) {
        if (md5Helper == null) {
            try {
                md5Helper = MessageDigest.getInstance("MD5");
            } catch (NoSuchAlgorithmException e) {
                log.log(Level.SEVERE, LogFacade.CANNOT_GET_MD5_DIGEST_EXCEPTION, e);
                throw new IllegalStateException(e.getMessage());
            }
        }

        if (hasMessageDigest()) {
            // Use pre-generated digest
            return getPassword(username);
        }

        char[] pwd = getPassword(username);
        int usernameLength = ((username != null) ? username.length() : 0);
        int realmNameLength = ((realmName != null) ? realmName.length() : 0);
        int pwdLength = ((pwd != null) ? pwd.length : 0);

        // digestValue = username:realmName:pwd
        char[] digestValue = new char[usernameLength + 1 + realmNameLength + 1 + pwdLength];
        int ind = 0;
        if (username != null) {
            System.arraycopy(username.toCharArray(), 0, digestValue, 0, usernameLength);
            ind = usernameLength;
        }
        digestValue[ind++] = ':';
        if (realmName != null) {
            System.arraycopy(realmName.toCharArray(), 0, digestValue, ind, realmNameLength);
            ind += realmNameLength;
        }
        digestValue[ind++] = ':';
        if (pwd != null) {
            System.arraycopy(pwd, 0, digestValue, ind, pwdLength);
        }

        byte[] valueBytes = null;
        try {
            valueBytes = Utility.convertCharArrayToByteArray(digestValue, getDigestEncoding());
        } catch (CharacterCodingException cce) {
            String msg = MessageFormat.format(rb.getString(LogFacade.ILLEGAL_DIGEST_ENCODING_EXCEPTION), getDigestEncoding());
            log.log(Level.SEVERE, msg, cce);
            throw new IllegalArgumentException(cce.getMessage());
        }

        byte[] digest = null;
        // Bugzilla 32137
        synchronized (md5Helper) {
            digest = md5Helper.digest(valueBytes);
        }

        return md5Encoder.encode(digest);
    }

    /**
     * Return a short name for this Realm implementation, for use in log messages.
     */
    protected abstract String getName();

    /**
     * Return the password associated with the given principal's user name.
     */
    protected abstract char[] getPassword(String username);

    /**
     * Return the Principal associated with the given user name.
     */
    protected abstract Principal getPrincipal(String username);

    /**
     * Log a message on the Logger associated with our Container (if any)
     *
     * @param message Message to be logged
     */
    protected void log(String message) {
        org.apache.catalina.Logger logger = null;
        String name = null;
        if (container != null) {
            logger = container.getLogger();
            name = container.getName();
        }
        if (logger != null) {
            logger.log(getName() + "[" + name + "]: " + message);
        } else {
            if (log.isLoggable(Level.INFO)) {
                log.log(Level.INFO, "{0}[{1}]: {2}", new Object[]{getName(), name, message});
            }
        }
    }

    /**
     * Log a message on the Logger associated with our Container (if any)
     *
     * @param message Message to be logged
     * @param t Associated exception
     */
    protected void log(String message, Throwable t) {
        org.apache.catalina.Logger logger = null;
        String name = null;
        if (container != null) {
            logger = container.getLogger();
            name = container.getName();
        }
        if (logger != null) {
            logger.log(getName() + "[" + name + "]: " + message, t, org.apache.catalina.Logger.WARNING);
        } else {
            log.log(Level.WARNING, getName() + "[" + name + "]: " + message, t);
        }
    }

    // START SJSAS 6202703
    protected void disableProxyCaching(HttpRequest request, HttpResponse response, boolean disableProxyCaching,
            boolean securePagesWithPragma) {
        HttpServletRequest hsrequest = (HttpServletRequest) request.getRequest();
        // Make sure that constrained resources are not cached by web proxies
        // or browsers as caching can provide a security hole
        if (disableProxyCaching && !"POST".equalsIgnoreCase(hsrequest.getMethod()) && (!checkIfRequestIsSecure || !hsrequest.isSecure())) {
            HttpServletResponse sresponse = (HttpServletResponse) response.getResponse();
            if (securePagesWithPragma) {
                // FIXME: These cause problems with downloading office docs
                // from IE under SSL and may not be needed for newer Mozilla
                // clients.
                sresponse.setHeader("Pragma", "No-cache");
                sresponse.setHeader("Cache-Control", "no-cache");
            } else {
                sresponse.setHeader("Cache-Control", "private");
            }
            sresponse.setHeader("Expires", DATE_ONE);
        }
    }
    // END SJSAS 6202703

    // -------------------- JMX and Registration --------------------

    protected ObjectName controller;

    public ObjectName getController() {
        return controller;
    }

    public void setController(ObjectName controller) {
        this.controller = controller;
    }

    // BEGIN IASRI 4808401, 4934562
    /**
     * Return an alternate principal from the request if available. Tomcat realms do not implement this so always return
     * null as default.
     *
     * @param req The request object.
     * @return Alternate principal or null.
     */
    @Override
    public Principal getAlternatePrincipal(HttpRequest req) {
        return null;
    }

    /**
     * Return an alternate auth type from the request if available. Tomcat realms do not implement this so always return
     * null as default.
     *
     * @param req The request object.
     * @return Alternate auth type or null.
     */
    @Override
    public String getAlternateAuthType(HttpRequest req) {
        return null;
    }
    // END IASRI 4808401

    // BEGIN IASRI 4856062,4918627,4874504
    /**
     * Set the name of the associated realm.
     *
     * @param name the name of the realm.
     */
    @Override
    public void setRealmName(String name, String authMethod) {
        // DO NOTHING. PRIVATE EXTENSION
    }

    /**
     * Returns the name of the associated realm.
     *
     * @return realm name or null if not set.
     */
    @Override
    public String getRealmName() {
        // DO NOTHING. PRIVATE EXTENSION
        return null;
    }

    // END IASRI 4856062,4918627,4874504
    @Override
    public Principal authenticate(HttpServletRequest hreq) {
        throw new UnsupportedOperationException();
    }

    private boolean equalsIgnoreCase(char[] arr1, char[] arr2) {
        if (arr1 == null) {
            return (arr2 == null);
        } else { // arr1 is not null
            if (arr2 == null || arr1.length != arr2.length) {
                return false;
            }
        }

        // here, arr1 and arr2 are not null with equal length
        boolean result = true;
        for (int i = 0; i < arr1.length; i++) {
            if (Character.toLowerCase(arr1[i]) != Character.toLowerCase(arr2[i])) {
                return false;
            }
        }

        return result;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy