Code cleanup
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / idp / authn / provider / UsernamePasswordLoginServlet.java
1 /*
2  * Copyright [2006] [University Corporation for Advanced Internet Development, Inc.]
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package edu.internet2.middleware.shibboleth.idp.authn.provider;
18
19 import java.io.IOException;
20 import java.security.Principal;
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.Set;
24
25 import javax.security.auth.Subject;
26 import javax.security.auth.callback.Callback;
27 import javax.security.auth.callback.CallbackHandler;
28 import javax.security.auth.callback.NameCallback;
29 import javax.security.auth.callback.PasswordCallback;
30 import javax.security.auth.callback.UnsupportedCallbackException;
31 import javax.security.auth.login.LoginException;
32 import javax.servlet.ServletException;
33 import javax.servlet.http.HttpServlet;
34 import javax.servlet.http.HttpServletRequest;
35 import javax.servlet.http.HttpServletResponse;
36
37 import org.opensaml.util.URLBuilder;
38 import org.opensaml.xml.util.DatatypeHelper;
39 import org.opensaml.xml.util.Pair;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 import edu.internet2.middleware.shibboleth.idp.authn.AuthenticationEngine;
44 import edu.internet2.middleware.shibboleth.idp.authn.LoginHandler;
45
46 /**
47  * This servlet should be protected by a filter which populates REMOTE_USER. The serlvet will then set the remote user
48  * field in a LoginContext.
49  */
50 public class UsernamePasswordLoginServlet extends HttpServlet {
51
52     /** Serial version UID. */
53     private static final long serialVersionUID = -572799841125956990L;
54
55     /** Class logger. */
56     private final Logger log = LoggerFactory.getLogger(RemoteUserAuthServlet.class);
57
58     /** Name of JAAS configuration used to authenticate users. */
59     private String jaasConfigName = "ShibUserPassAuth";
60
61     /** init-param which can be passed to the servlet to override the default JAAS config. */
62     private final String jaasInitParam = "jaasConfigName";
63
64     /** Login page name. */
65     private String loginPage = "login.jsp";
66
67     /** init-param which can be passed to the servlet to override the default login page. */
68     private final String loginPageInitParam = "loginPage";
69
70     /** Parameter name to indicate login failure. */
71     private final String failureParam = "loginFailed";
72
73     /** HTTP request parameter containing the user name. */
74     private final String usernameAttribute = "j_username";
75
76     /** HTTP request parameter containing the user's password. */
77     private final String passwordAttribute = "j_password";
78
79     /** {@inheritDoc} */
80     public void init() {
81         if (getInitParameter(jaasInitParam) != null) {
82             jaasConfigName = getInitParameter(jaasInitParam);
83         }
84         if (getInitParameter(loginPageInitParam) != null) {
85             loginPage = getInitParameter(loginPageInitParam);
86         }
87     }
88
89     /** {@inheritDoc} */
90     protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException,
91             IOException {
92         String username = DatatypeHelper.safeTrimOrNullString(request.getParameter(usernameAttribute));
93         String password = DatatypeHelper.safeTrimOrNullString(request.getParameter(passwordAttribute));
94
95         if (username == null || password == null) {
96             redirectToLoginPage(request, response, null);
97             return;
98         }
99
100         if (authenticateUser(request)) {
101             AuthenticationEngine.returnToAuthenticationEngine(request, response);
102         } else {
103             List<Pair<String, String>> queryParams = new ArrayList<Pair<String, String>>();
104             queryParams.add(new Pair<String, String>(failureParam, "true"));
105             redirectToLoginPage(request, response, queryParams);
106         }
107     }
108
109     /**
110      * Sends the user to the login page.
111      * 
112      * @param request current request
113      * @param response current response
114      * @param queryParams query parameters to pass to the login page
115      */
116     protected void redirectToLoginPage(HttpServletRequest request, HttpServletResponse response,
117             List<Pair<String, String>> queryParams) {
118         try {
119             StringBuilder pathBuilder = new StringBuilder();
120             pathBuilder.append(request.getContextPath());
121             pathBuilder.append("/");
122             pathBuilder.append(loginPage);
123
124             URLBuilder urlBuilder = new URLBuilder();
125             urlBuilder.setScheme(request.getScheme());
126             urlBuilder.setHost(request.getLocalName());
127             urlBuilder.setPort(request.getLocalPort());
128             urlBuilder.setPath(pathBuilder.toString());
129
130             if (queryParams == null) {
131                 queryParams = new ArrayList<Pair<String, String>>();
132             }
133             
134             queryParams.add(new Pair<String, String>("actionUrl", request.getContextPath()
135                     + request.getServletPath()));
136             urlBuilder.getQueryParams().addAll(queryParams);
137
138             log.debug("Redirecting to login page {}", urlBuilder.buildURL());
139             response.sendRedirect(urlBuilder.buildURL());
140             return;
141         } catch (IOException ex) {
142             log.error("Unable to redirect to login page.", ex);
143         }
144     }
145
146     /**
147      * Authenticate a username and password against JAAS. If authentication succeeds the name of the first principal, or
148      * the username if that is empty, and the subject are placed into the request in their respective attributes.
149      * 
150      * @param request current authentication request
151      * 
152      * @return true of authentication succeeds, false if not
153      */
154     protected boolean authenticateUser(HttpServletRequest request) {
155
156         try {
157             String username = DatatypeHelper.safeTrimOrNullString(request.getParameter(usernameAttribute));
158             String password = DatatypeHelper.safeTrimOrNullString(request.getParameter(passwordAttribute));
159
160             SimpleCallbackHandler cbh = new SimpleCallbackHandler(username, password);
161
162             javax.security.auth.login.LoginContext jaasLoginCtx = new javax.security.auth.login.LoginContext(
163                     jaasConfigName, cbh);
164
165             jaasLoginCtx.login();
166             log.debug("Successfully authenticated user {}", username);
167
168             Subject subject = jaasLoginCtx.getSubject();
169             Set<Principal> principals = subject.getPrincipals();
170
171             if (principals.isEmpty()) {
172                 request.setAttribute(LoginHandler.PRINCIPAL_NAME_KEY, username);
173             } else {
174                 Principal principal = principals.iterator().next();
175                 String principalName = DatatypeHelper.safeTrimOrNullString(principal.getName());
176                 if (principalName == null) {
177                     request.setAttribute(LoginHandler.PRINCIPAL_NAME_KEY, username);
178                 } else {
179                     request.setAttribute(LoginHandler.PRINCIPAL_NAME_KEY, principal.getName());
180                 }
181                 request.setAttribute(LoginHandler.SUBJECT_KEY, jaasLoginCtx.getSubject());
182             }
183
184             return true;
185         } catch (LoginException e) {
186             log.debug("User authentication failed", e);
187             return false;
188         }
189     }
190
191     /**
192      * A callback handler that provides static name and password data to a JAAS loging process.
193      * 
194      * This handler only supports {@link NameCallback} and {@link PasswordCallback}.
195      */
196     protected class SimpleCallbackHandler implements CallbackHandler {
197
198         /** Name of the user. */
199         private String uname;
200
201         /** User's password. */
202         private String pass;
203
204         /**
205          * Constructor.
206          * 
207          * @param username The username
208          * @param password The password
209          */
210         public SimpleCallbackHandler(String username, String password) {
211             uname = username;
212             pass = password;
213         }
214
215         /**
216          * Handle a callback.
217          * 
218          * @param callbacks The list of callbacks to process.
219          * 
220          * @throws UnsupportedCallbackException If callbacks has a callback other than {@link NameCallback} or
221          *             {@link PasswordCallback}.
222          */
223         public void handle(final Callback[] callbacks) throws UnsupportedCallbackException {
224
225             if (callbacks == null || callbacks.length == 0) {
226                 return;
227             }
228
229             for (Callback cb : callbacks) {
230                 if (cb instanceof NameCallback) {
231                     NameCallback ncb = (NameCallback) cb;
232                     ncb.setName(uname);
233                 } else if (cb instanceof PasswordCallback) {
234                     PasswordCallback pcb = (PasswordCallback) cb;
235                     pcb.setPassword(pass.toCharArray());
236                 }
237             }
238         }
239     }
240 }