2 * Copyright 2008, 2009 Electronic Business Systems Ltd.
4 * This file is part of GSS.
6 * GSS is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
11 * GSS is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with GSS. If not, see <http://www.gnu.org/licenses/>.
19 package gr.ebs.gss.server;
21 import static gr.ebs.gss.server.configuration.GSSConfigurationFactory.getConfiguration;
22 import gr.ebs.gss.client.exceptions.DuplicateNameException;
23 import gr.ebs.gss.client.exceptions.ObjectNotFoundException;
24 import gr.ebs.gss.client.exceptions.RpcException;
25 import gr.ebs.gss.server.domain.Nonce;
26 import gr.ebs.gss.server.domain.User;
27 import gr.ebs.gss.server.domain.UserLogin;
29 import java.io.IOException;
30 import java.io.PrintWriter;
32 import java.net.URISyntaxException;
33 import java.net.URLEncoder;
34 import java.util.Date;
36 import javax.servlet.http.Cookie;
37 import javax.servlet.http.HttpServletRequest;
38 import javax.servlet.http.HttpServletResponse;
40 import org.apache.commons.codec.binary.Base64;
41 import org.apache.commons.logging.Log;
42 import org.apache.commons.logging.LogFactory;
45 * The servlet that handles user logins.
49 public class Login extends BaseServlet {
51 * The request parameter name for the nonce.
53 private static final String NONCE_PARAM = "nonce";
56 * The request parameter name for the URL to redirect
57 * to after authentication.
59 private static final String NEXT_URL_PARAM = "next";
62 * The serial version UID of the class.
64 private static final long serialVersionUID = 1L;
67 * The name of the authentication cookie.
69 public static final String AUTH_COOKIE = "_gss_a";
72 * The separator character for the authentication cookie.
74 public static final char COOKIE_SEPARATOR = '|';
77 * The name of the the webdav cookie.
79 public static final String WEBDAV_COOKIE = "_gss_wd";
84 private static Log logger = LogFactory.getLog(Login.class);
87 public void service(HttpServletRequest request, HttpServletResponse response) throws IOException {
88 // Fetch the next URL to display, if any.
89 String nextUrl = request.getParameter(NEXT_URL_PARAM);
90 // Fetch the supplied nonce, if any.
91 String nonce = request.getParameter(NONCE_PARAM);
92 String[] attrs = new String[] {"REMOTE_USER", "HTTP_SHIB_INETORGPERSON_DISPLAYNAME",
93 "HTTP_SHIB_INETORGPERSON_GIVENNAME", "HTTP_SHIB_PERSON_COMMONNAME",
94 "HTTP_SHIB_PERSON_SURNAME", "HTTP_SHIB_INETORGPERSON_MAIL",
95 "HTTP_SHIB_EP_UNSCOPEDAFFILIATION", "HTTP_PERSISTENT_ID"};
96 StringBuilder buf = new StringBuilder("Shibboleth Attributes\n");
97 for (String attr: attrs)
98 buf.append(attr+": ").append(request.getAttribute(attr)).append('\n');
100 if (logger.isDebugEnabled()) {
101 buf = new StringBuilder("Shibboleth Attributes as bytes\n");
102 for (String attr: attrs)
103 if (request.getAttribute(attr) != null)
104 buf.append(attr+": ").append(getHexString(request.getAttribute(attr).toString().getBytes("UTF-8"))).append('\n');
108 response.setContentType("text/html");
109 Object usernameAttr = request.getAttribute("REMOTE_USER");
110 Object nameAttr = request.getAttribute("HTTP_SHIB_INETORGPERSON_DISPLAYNAME");
111 Object givennameAttr = request.getAttribute("HTTP_SHIB_INETORGPERSON_GIVENNAME"); // Multi-valued
112 Object cnAttr = request.getAttribute("HTTP_SHIB_PERSON_COMMONNAME"); // Multi-valued
113 Object snAttr = request.getAttribute("HTTP_SHIB_PERSON_SURNAME"); // Multi-valued
114 Object mailAttr = request.getAttribute("HTTP_SHIB_INETORGPERSON_MAIL"); // Multi-valued
115 Object persistentIdAttr = request.getAttribute("HTTP_PERSISTENT_ID");
116 // Use a configured test username if found, as a shortcut for development deployments.
117 String gwtServer = null;
118 if (getConfiguration().getString("testUsername") != null) {
119 usernameAttr = getConfiguration().getString("testUsername");
120 // Fetch the GWT code server URL, if any.
121 gwtServer = request.getParameter(GWT_SERVER_PARAM);
123 if (usernameAttr == null) {
124 String authErrorUrl = "authenticationError.jsp";
125 authErrorUrl += "?name=" + (nameAttr==null? "-": nameAttr.toString());
126 authErrorUrl += "&givenname=" + (givennameAttr==null? "-": givennameAttr.toString());
127 authErrorUrl += "&sn=" + (snAttr==null? "-": snAttr.toString());
128 authErrorUrl += "&cn=" + (cnAttr==null? "-": cnAttr.toString());
129 authErrorUrl += "&mail=" + (mailAttr==null? "-": mailAttr.toString());
130 response.sendRedirect(authErrorUrl);
133 String username = decodeAttribute(usernameAttr);
135 if (nameAttr != null && !nameAttr.toString().isEmpty())
136 name = decodeAttribute(nameAttr);
137 else if (cnAttr != null && !cnAttr.toString().isEmpty()) {
138 name = decodeAttribute(cnAttr);
139 if (name.indexOf(';') != -1)
140 name = name.substring(0, name.indexOf(';'));
141 } else if (givennameAttr != null && snAttr != null && !givennameAttr.toString().isEmpty() && !snAttr.toString().isEmpty()) {
142 String givenname = decodeAttribute(givennameAttr);
143 if (givenname.indexOf(';') != -1)
144 givenname = givenname.substring(0, givenname.indexOf(';'));
145 String sn = decodeAttribute(snAttr);
146 if (sn.indexOf(';') != -1)
147 sn = sn.substring(0, sn.indexOf(';'));
148 name = givenname + ' ' + sn;
149 } else if (givennameAttr == null && snAttr != null && !snAttr.toString().isEmpty()) {
150 name = decodeAttribute(snAttr);
151 if (name.indexOf(';') != -1)
152 name = name.substring(0, name.indexOf(';'));
155 String mail = mailAttr != null ? mailAttr.toString() : username;
156 if (mail.indexOf(';') != -1)
157 mail = mail.substring(0, mail.indexOf(';'));
158 String persistentId = persistentIdAttr != null ? persistentIdAttr.toString() : "";
161 if (!persistentId.isEmpty()) {
162 int bang = persistentId.indexOf('!');
164 idp = persistentId.substring(0, bang);
165 idpid = persistentId.substring(bang + 1);
169 user = getService().findUser(username);
171 user = getService().createUser(username, name, mail, idp, idpid);
172 if (!user.isActive()) {
173 logger.info("Disabled user " + username + " tried to login.");
174 response.sendError(HttpServletResponse.SC_FORBIDDEN, "This account is disabled");
177 if (!user.hasAcceptedPolicy()) {
178 String policyUrl = "policy.jsp";
179 if (request.getQueryString() != null)
180 policyUrl += "?user=" + username + "&" + request.getQueryString();
181 response.sendRedirect(policyUrl);
186 user.setIdentityProvider(idp);
187 user.setIdentityProviderId(idpid);
189 UserLogin userLogin = new UserLogin();
190 userLogin.setLoginDate(new Date());
191 userLogin.setUser(user);
192 if (user.getAuthToken() == null)
193 user = getService().updateUserToken(user.getId());
194 // Set WebDAV password to token if it's never been set.
195 if (user.getWebDAVPassword() == null || user.getWebDAVPassword().length() == 0) {
196 String tokenEncoded = new String(Base64.encodeBase64(user.getAuthToken()), "US-ASCII");
197 user.setWebDAVPassword(tokenEncoded);
199 // Set the default user class if none was set.
200 if (user.getUserClass() == null)
201 user.setUserClass(getService().getUserClasses().get(0));
202 getService().updateUser(user);
203 getService().addUserLogin(userLogin);
204 } catch (RpcException e) {
205 String error = "An error occurred while communicating with the service";
206 logger.error(error, e);
207 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
209 } catch (DuplicateNameException e) {
210 String error = "User with username " + username + " already exists";
211 logger.error(error, e);
212 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
214 } catch (ObjectNotFoundException e) {
215 String error = "No username was provided";
216 logger.error(error, e);
217 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
220 String tokenEncoded = new String(Base64.encodeBase64(user.getAuthToken()), "US-ASCII");
221 String userEncoded = URLEncoder.encode(user.getUsername(), "US-ASCII");
222 if (logger.isDebugEnabled())
223 logger.debug("user: "+userEncoded+" token: "+tokenEncoded);
224 if (nextUrl != null && !nextUrl.isEmpty()) {
226 if (gwtServer != null)
227 nextUrl += '?' + GWT_SERVER_PARAM + '=' + gwtServer;
229 next = new URI(nextUrl);
230 } catch (URISyntaxException e) {
231 response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage());
234 if ("x-gr-ebs-igss".equalsIgnoreCase(next.getScheme()))
235 nextUrl += "?u=" + userEncoded + "&t=" + tokenEncoded;
237 String domain = next.getHost();
238 String path = getServletContext().getContextPath() + '/';
239 Cookie cookie = new Cookie(AUTH_COOKIE, userEncoded + COOKIE_SEPARATOR +
241 cookie.setMaxAge(-1);
242 cookie.setDomain(domain);
243 cookie.setPath(path);
244 response.addCookie(cookie);
245 cookie = new Cookie(WEBDAV_COOKIE, user.getWebDAVPassword());
246 cookie.setMaxAge(-1);
247 cookie.setDomain(domain);
248 cookie.setPath(path);
249 response.addCookie(cookie);
251 response.sendRedirect(nextUrl);
252 } else if (nonce != null) {
253 nonce = URLEncoder.encode(nonce, "US-ASCII");
256 if (logger.isDebugEnabled())
257 logger.debug("user: "+user.getId()+" nonce: "+nonce);
258 n = getService().getNonce(nonce, user.getId());
259 } catch (ObjectNotFoundException e) {
260 PrintWriter out = response.getWriter();
261 out.println("<HTML>");
262 out.println("<HEAD><TITLE>" + getServiceName() + " Authentication</TITLE>" +
263 "<LINK TYPE='text/css' REL='stylesheet' HREF='gss.css'></HEAD>");
264 out.println("<BODY><CENTER><P>");
265 out.println("The supplied nonce could not be found!");
266 out.println("</CENTER></BODY></HTML>");
268 } catch (RpcException e) {
269 String error = "An error occurred while communicating with the service";
270 logger.error(error, e);
271 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
275 getService().activateUserNonce(user.getId(), nonce, n.getNonceExpiryDate());
276 } catch (ObjectNotFoundException e) {
277 String error = "Unable to find user";
278 logger.error(error, e);
279 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
281 } catch (RpcException e) {
282 String error = "An error occurred while communicating with the service";
283 logger.error(error, e);
284 response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error);
288 getService().removeNonce(n.getId());
289 } catch (ObjectNotFoundException e) {
290 logger.info("Nonce already removed!", e);
291 } catch (RpcException e) {
292 logger.warn("Could not remove nonce from data store", e);
294 PrintWriter out = response.getWriter();
295 out.println("<HTML>");
296 out.println("<HEAD><TITLE>" + getServiceName() + " Authentication</TITLE>" +
297 "<LINK TYPE='text/css' REL='stylesheet' HREF='gss.css'></HEAD>");
298 out.println("<BODY><CENTER><P>");
299 out.println("You can now close this browser window and return to your application.");
300 out.println("</CENTER></BODY></HTML>");
302 PrintWriter out = response.getWriter();
303 out.println("<HTML>");
304 out.println("<HEAD><TITLE>" + getServiceName() + " Authentication</TITLE>" +
305 "<LINK TYPE='text/css' REL='stylesheet' HREF='gss.css'></HEAD>");
306 out.println("<BODY><CENTER><P>");
307 out.println("Name: " + user.getName() + "<BR>");
308 out.println("E-mail: " + user.getEmail() + "<BR><P>");
309 out.println("Username: " + user.getUsername() + "<BR>");
310 out.println("Athentication token: " + tokenEncoded + "<BR>");
311 out.println("</CENTER></BODY></HTML>");