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,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.shiro.web.filter.authc;
020
021import org.apache.shiro.authc.AuthenticationException;
022import org.apache.shiro.authc.AuthenticationToken;
023import org.apache.shiro.authc.UsernamePasswordToken;
024import org.apache.shiro.authz.UnauthenticatedException;
025import org.apache.shiro.subject.Subject;
026
027import javax.servlet.ServletException;
028import javax.servlet.ServletRequest;
029import javax.servlet.ServletResponse;
030import java.io.IOException;
031import java.util.Arrays;
032
033/**
034 * An <code>AuthenticationFilter</code> that is capable of automatically performing an authentication attempt
035 * based on the incoming request.
036 *
037 * @since 0.9
038 */
039public abstract class AuthenticatingFilter extends AuthenticationFilter {
040    public static final String PERMISSIVE = "permissive";
041
042    //TODO - complete JavaDoc
043
044    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
045        AuthenticationToken token = createToken(request, response);
046        if (token == null) {
047            String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
048                    "must be created in order to execute a login attempt.";
049            throw new IllegalStateException(msg);
050        }
051        try {
052            Subject subject = getSubject(request, response);
053            subject.login(token);
054            return onLoginSuccess(token, subject, request, response);
055        } catch (AuthenticationException e) {
056            return onLoginFailure(token, e, request, response);
057        }
058    }
059
060    protected abstract AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception;
061
062    protected AuthenticationToken createToken(String username, String password,
063                                              ServletRequest request, ServletResponse response) {
064        boolean rememberMe = isRememberMe(request);
065        String host = getHost(request);
066        return createToken(username, password, rememberMe, host);
067    }
068
069    protected AuthenticationToken createToken(String username, String password,
070                                              boolean rememberMe, String host) {
071        return new UsernamePasswordToken(username, password, rememberMe, host);
072    }
073
074    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
075                                     ServletRequest request, ServletResponse response) throws Exception {
076        return true;
077    }
078
079    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
080                                     ServletRequest request, ServletResponse response) {
081        return false;
082    }
083
084    /**
085     * Returns the host name or IP associated with the current subject.  This method is primarily provided for use
086     * during construction of an <code>AuthenticationToken</code>.
087     * <p/>
088     * The default implementation merely returns {@link ServletRequest#getRemoteHost()}.
089     *
090     * @param request the incoming ServletRequest
091     * @return the <code>InetAddress</code> to associate with the login attempt.
092     */
093    protected String getHost(ServletRequest request) {
094        return request.getRemoteHost();
095    }
096
097    /**
098     * Returns <code>true</code> if &quot;rememberMe&quot; should be enabled for the login attempt associated with the
099     * current <code>request</code>, <code>false</code> otherwise.
100     * <p/>
101     * This implementation always returns <code>false</code> and is provided as a template hook to subclasses that
102     * support <code>rememberMe</code> logins and wish to determine <code>rememberMe</code> in a custom mannner
103     * based on the current <code>request</code>.
104     *
105     * @param request the incoming ServletRequest
106     * @return <code>true</code> if &quot;rememberMe&quot; should be enabled for the login attempt associated with the
107     *         current <code>request</code>, <code>false</code> otherwise.
108     */
109    protected boolean isRememberMe(ServletRequest request) {
110        return false;
111    }
112
113    /**
114     * Determines whether the current subject should be allowed to make the current request.
115     * <p/>
116     * The default implementation returns <code>true</code> if the user is authenticated.  Will also return
117     * <code>true</code> if the {@link #isLoginRequest} returns false and the &quot;permissive&quot; flag is set.
118     *
119     * @return <code>true</code> if request should be allowed access
120     */
121    @Override
122    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
123        return super.isAccessAllowed(request, response, mappedValue) ||
124                (!isLoginRequest(request, response) && isPermissive(mappedValue));
125    }
126
127    /**
128     * Returns <code>true</code> if the mappedValue contains the {@link #PERMISSIVE} qualifier.
129     *
130     * @return <code>true</code> if this filter should be permissive
131     */
132    protected boolean isPermissive(Object mappedValue) {
133        if(mappedValue != null) {
134            String[] values = (String[]) mappedValue;
135            return Arrays.binarySearch(values, PERMISSIVE) >= 0;
136        }
137        return false;
138    }
139
140    /**
141     * Overrides the default behavior to call {@link #onAccessDenied} and swallow the exception if the exception is
142     * {@link UnauthenticatedException}.
143     */
144    @Override
145    protected void cleanup(ServletRequest request, ServletResponse response, Exception existing) throws ServletException, IOException {
146        if (existing instanceof UnauthenticatedException || (existing instanceof ServletException && existing.getCause() instanceof UnauthenticatedException))
147        {
148            try {
149                onAccessDenied(request, response);
150                existing = null;
151            } catch (Exception e) {
152                existing = e;
153            }
154        }
155        super.cleanup(request, response, existing);
156
157    }
158}