001/**
002 * Copyright 2005-2018 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krad.web.filter;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.core.api.config.property.ConfigContext;
020import org.kuali.rice.kim.api.identity.IdentityService;
021import org.kuali.rice.kim.api.identity.principal.Principal;
022import org.kuali.rice.kim.api.services.KimApiServiceLocator;
023import org.kuali.rice.krad.UserSession;
024import org.kuali.rice.krad.exception.AuthenticationException;
025import org.kuali.rice.krad.util.KRADConstants;
026import org.kuali.rice.krad.util.KRADUtils;
027
028import javax.servlet.Filter;
029import javax.servlet.FilterChain;
030import javax.servlet.FilterConfig;
031import javax.servlet.ServletException;
032import javax.servlet.ServletRequest;
033import javax.servlet.ServletResponse;
034import javax.servlet.http.HttpServletRequest;
035import javax.servlet.http.HttpServletRequestWrapper;
036import javax.servlet.http.HttpServletResponse;
037import java.io.IOException;
038import java.net.URLEncoder;
039
040/**
041 * A login filter which forwards to a login page that allows for the desired
042 * authentication ID to be entered (with testing password if option enabled)
043 *
044 * @author Kuali Rice Team (rice.collab@kuali.org)
045 */
046public class DummyLoginFilter implements Filter {
047    private String loginPath;
048    private boolean showPassword = false;
049
050    @Override
051        public void init(FilterConfig config) throws ServletException {
052        loginPath = ConfigContext.getCurrentContextConfig().getProperty("loginPath");
053        showPassword = Boolean.valueOf(ConfigContext.getCurrentContextConfig().getProperty("showPassword")).booleanValue();
054
055        if (loginPath == null) {
056            loginPath = "/kr-login/login?viewId=DummyLoginView";
057        }
058    }
059
060        @Override
061        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
062                this.doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
063        }
064
065        private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
066        final UserSession session = KRADUtils.getUserSessionFromRequest(request);
067
068        if (session == null) {
069            loginRequired(request, response, chain);
070
071            return;
072
073        } else {
074            // Perform request as signed in user
075            request = new HttpServletRequestWrapper(request) {
076                @Override
077                public String getRemoteUser() {
078                    return session.getPrincipalName();
079                }
080            };
081        }
082        chain.doFilter(request, response);
083    }
084
085    private void loginRequired(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
086        if (StringUtils.isNotBlank(request.getParameter("__login_user"))) {
087            performLoginAttempt(request, response);
088        } else {
089            // ignore ajax calls from login screen
090            if (StringUtils.equals(request.getPathInfo(),"/listener")) {
091               return;
092            }
093
094            // allow redirect and form submit from login screen
095            if (StringUtils.equals(request.getPathInfo(),"/login")) {
096                chain.doFilter(request, response);
097            } else {
098                // no session has been established and this is not a login form submission, so redirect to login page
099                response.sendRedirect(getLoginRedirectUrl(request));
100            }
101        }
102    }
103
104    private void performLoginAttempt(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
105        IdentityService auth = KimApiServiceLocator.getIdentityService();
106        final String user = request.getParameter("__login_user");
107        String password = request.getParameter("__login_pw");
108
109        // if passwords are used, they cannot be blank
110        if (showPassword && StringUtils.isBlank(password)) {
111            handleInvalidLogin(request, response);
112            return;
113        }
114
115        // Very simple password checking. Nothing hashed or encrypted. This is strictly for demonstration purposes only.
116        //    password must have non null value on krim_prncpl_t record
117        Principal principal = showPassword ? auth.getPrincipalByPrincipalNameAndPassword(user, password) : auth.getPrincipalByPrincipalName(user);
118        if (principal == null) {
119            handleInvalidLogin(request, response);
120            return;
121        }
122
123        UserSession userSession = new UserSession(user);
124
125        // Test if session was successfully build for this user
126        if ( userSession.getPerson() == null ) {
127            throw new AuthenticationException("Invalid User: " + user  );
128        }
129
130        request.getSession().setAttribute(KRADConstants.USER_SESSION_KEY, userSession);
131
132        // wrap the request with the signed in user
133        // UserLoginFilter and WebAuthenticationService will build the session
134        request = new HttpServletRequestWrapper(request) {
135            @Override
136            public String getRemoteUser() {
137                return user;
138            }
139        };
140
141        StringBuilder redirectUrl = new StringBuilder(ConfigContext.getCurrentContextConfig().getProperty(KRADConstants.APPLICATION_URL_KEY));
142        redirectUrl.append(findTargetUrl(request));
143        response.sendRedirect(redirectUrl.toString());
144    }
145
146    /**
147     * Handles and invalid login attempt.
148     *
149     * Sets error message and redirects to login screen
150     *
151     * @param request the incoming request
152     * @param response the outgoing response
153     * @throws javax.servlet.ServletException if unable to handle the invalid login
154     * @throws java.io.IOException if unable to handle the invalid login
155     */
156        private void handleInvalidLogin(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
157        StringBuilder redirectUrl = new StringBuilder(getLoginRedirectUrl(request));
158        redirectUrl.append("&login_message=Invalid Login");
159        response.sendRedirect(redirectUrl.toString());
160    }
161
162    @Override
163        public void destroy() {
164        loginPath = null;
165    }
166
167    /**
168     * Construct Url to login screen with original target Url in returnLocation property
169     *
170     * @param request
171     * @return Url string
172     * @throws IOException
173     */
174    private String getLoginRedirectUrl(HttpServletRequest request) throws IOException {
175        String targetUrl = findTargetUrl(request);
176
177        StringBuilder redirectUrl = new StringBuilder(ConfigContext.getCurrentContextConfig().getProperty(KRADConstants.APPLICATION_URL_KEY));
178        redirectUrl.append(loginPath);
179        redirectUrl.append("&returnLocation=");
180        redirectUrl.append(URLEncoder.encode(targetUrl,"UTF-8"));
181
182        return redirectUrl.toString();
183    }
184
185    /**
186     * Construct a url from a HttpServletRequest with login properties removed
187     *
188     * @param request
189     * @return Url string
190     */
191    private String findTargetUrl(HttpServletRequest request) {
192        StringBuilder targetUrl = new StringBuilder();
193        targetUrl.append(request.getServletPath());
194
195        if (StringUtils.isNotBlank(request.getPathInfo())) {
196            targetUrl.append(request.getPathInfo());
197        }
198
199        // clean login params from query string
200        if (StringUtils.isNotBlank(request.getQueryString())) {
201            targetUrl.append("?");
202
203            for (String keyValuePair : request.getQueryString().split("&")) {
204                if (isValidProperty(keyValuePair)) targetUrl.append("&").append(keyValuePair);
205            }
206
207        }
208
209        // clean up delimiters and return url string
210        return targetUrl.toString().replace("&&","&").replace("?&","?");
211    }
212
213    /**
214     * Test if property is needed (ie: Not a login property)
215     *
216     * @param keyValuePair
217     * @return Boolean
218     */
219    private Boolean isValidProperty(String keyValuePair) {
220        int eq = keyValuePair.indexOf("=");
221
222        if (eq < 0) {
223            // key with no value
224            return Boolean.FALSE;
225        }
226
227        String key = keyValuePair.substring(0, eq);
228        if (!key.equals("__login_pw")
229                && !key.equals("__login_user")
230                && !key.equals("login_message")) {
231            return Boolean.TRUE;
232        }
233
234        return Boolean.FALSE;
235    }
236
237}