root / src / gr / ebs / gss / server / Login.java @ c35f359f
History | View | Annotate | Download (14 kB)
1 |
/*
|
---|---|
2 |
* Copyright 2008, 2009 Electronic Business Systems Ltd.
|
3 |
*
|
4 |
* This file is part of GSS.
|
5 |
*
|
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.
|
10 |
*
|
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.
|
15 |
*
|
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/>.
|
18 |
*/
|
19 |
package gr.ebs.gss.server; |
20 |
|
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.ejb.ExternalAPI; |
28 |
|
29 |
import java.io.IOException; |
30 |
import java.io.PrintWriter; |
31 |
import java.io.UnsupportedEncodingException; |
32 |
import java.net.URI; |
33 |
import java.net.URISyntaxException; |
34 |
import java.net.URLEncoder; |
35 |
import java.util.Date; |
36 |
import java.util.Formatter; |
37 |
|
38 |
import javax.naming.Context; |
39 |
import javax.naming.InitialContext; |
40 |
import javax.naming.NamingException; |
41 |
import javax.rmi.PortableRemoteObject; |
42 |
import javax.servlet.http.Cookie; |
43 |
import javax.servlet.http.HttpServlet; |
44 |
import javax.servlet.http.HttpServletRequest; |
45 |
import javax.servlet.http.HttpServletResponse; |
46 |
|
47 |
import org.apache.commons.codec.binary.Base64; |
48 |
import org.apache.commons.logging.Log; |
49 |
import org.apache.commons.logging.LogFactory; |
50 |
|
51 |
/**
|
52 |
* The servlet that handles user logins.
|
53 |
*
|
54 |
* @author past
|
55 |
*/
|
56 |
public class Login extends HttpServlet { |
57 |
/**
|
58 |
* The request parameter name for the nonce.
|
59 |
*/
|
60 |
private static final String NONCE_PARAM = "nonce"; |
61 |
|
62 |
/**
|
63 |
* The request parameter name for the URL to redirect
|
64 |
* to after authentication.
|
65 |
*/
|
66 |
private static final String NEXT_URL_PARAM = "next"; |
67 |
|
68 |
/**
|
69 |
* The request parameter name for the GWT code server URL, used when
|
70 |
* debugging.
|
71 |
*/
|
72 |
private static final String GWT_SERVER_PARAM = "gwt.codesvr"; |
73 |
|
74 |
/**
|
75 |
* The serial version UID of the class.
|
76 |
*/
|
77 |
private static final long serialVersionUID = 1L; |
78 |
|
79 |
/**
|
80 |
* The name of the authentication cookie.
|
81 |
*/
|
82 |
private static final String AUTH_COOKIE = "_gss_a"; |
83 |
|
84 |
/**
|
85 |
* The separator character for the authentication cookie.
|
86 |
*/
|
87 |
private static final char COOKIE_SEPARATOR = '|'; |
88 |
|
89 |
/**
|
90 |
* The name of the the webdav cookie.
|
91 |
*/
|
92 |
public static final String WEBDAV_COOKIE = "_gss_wd"; |
93 |
|
94 |
/**
|
95 |
* The logger.
|
96 |
*/
|
97 |
private static Log logger = LogFactory.getLog(Login.class); |
98 |
|
99 |
/**
|
100 |
* A helper method that retrieves a reference to the ExternalAPI bean and
|
101 |
* stores it for future use.
|
102 |
*
|
103 |
* @return an ExternalAPI instance
|
104 |
* @throws RpcException in case an error occurs
|
105 |
*/
|
106 |
private ExternalAPI getService() throws RpcException { |
107 |
try {
|
108 |
final Context ctx = new InitialContext(); |
109 |
final Object ref = ctx.lookup(getConfiguration().getString("externalApiPath")); |
110 |
return (ExternalAPI) PortableRemoteObject.narrow(ref, ExternalAPI.class); |
111 |
} catch (final NamingException e) { |
112 |
logger.error("Unable to retrieve the ExternalAPI EJB", e);
|
113 |
throw new RpcException("An error occurred while contacting the naming service"); |
114 |
} |
115 |
} |
116 |
|
117 |
/**
|
118 |
* Return the name of the service.
|
119 |
*/
|
120 |
private String getServiceName() { |
121 |
return getConfiguration().getString("serviceName", "GSS"); |
122 |
} |
123 |
|
124 |
@Override
|
125 |
public void service(HttpServletRequest request, HttpServletResponse response) throws IOException { |
126 |
// Fetch the next URL to display, if any.
|
127 |
String nextUrl = request.getParameter(NEXT_URL_PARAM);
|
128 |
// Fetch the supplied nonce, if any.
|
129 |
String nonce = request.getParameter(NONCE_PARAM);
|
130 |
String[] attrs = new String[] {"REMOTE_USER", "HTTP_SHIB_INETORGPERSON_DISPLAYNAME", |
131 |
"HTTP_SHIB_INETORGPERSON_GIVENNAME", "HTTP_SHIB_PERSON_COMMONNAME", |
132 |
"HTTP_SHIB_PERSON_SURNAME", "HTTP_SHIB_INETORGPERSON_MAIL", |
133 |
"HTTP_SHIB_EP_UNSCOPEDAFFILIATION", "HTTP_PERSISTENT_ID"}; |
134 |
StringBuilder buf = new StringBuilder("Shibboleth Attributes\n"); |
135 |
for (String attr: attrs) |
136 |
buf.append(attr+": ").append(request.getAttribute(attr)).append('\n'); |
137 |
logger.info(buf); |
138 |
if (logger.isDebugEnabled()) {
|
139 |
buf = new StringBuilder("Shibboleth Attributes as bytes\n"); |
140 |
for (String attr: attrs) |
141 |
if (request.getAttribute(attr) != null) |
142 |
buf.append(attr+": ").append(getHexString(request.getAttribute(attr).toString().getBytes("UTF-8"))).append('\n'); |
143 |
logger.debug(buf); |
144 |
} |
145 |
User user = null;
|
146 |
response.setContentType("text/html");
|
147 |
Object usernameAttr = request.getAttribute("REMOTE_USER"); |
148 |
Object nameAttr = request.getAttribute("HTTP_SHIB_INETORGPERSON_DISPLAYNAME"); |
149 |
Object givennameAttr = request.getAttribute("HTTP_SHIB_INETORGPERSON_GIVENNAME"); // Multi-valued |
150 |
Object cnAttr = request.getAttribute("HTTP_SHIB_PERSON_COMMONNAME"); // Multi-valued |
151 |
Object snAttr = request.getAttribute("HTTP_SHIB_PERSON_SURNAME"); // Multi-valued |
152 |
Object mailAttr = request.getAttribute("HTTP_SHIB_INETORGPERSON_MAIL"); // Multi-valued |
153 |
Object userclassAttr = request.getAttribute("HTTP_SHIB_EP_UNSCOPEDAFFILIATION"); // Multi-valued |
154 |
Object persistentIdAttr = request.getAttribute("HTTP_PERSISTENT_ID"); |
155 |
// Use a configured test username if found, as a shortcut for development deployments.
|
156 |
String gwtServer = null; |
157 |
if (getConfiguration().getString("testUsername") != null) { |
158 |
usernameAttr = getConfiguration().getString("testUsername");
|
159 |
// Fetch the GWT code server URL, if any.
|
160 |
gwtServer = request.getParameter(GWT_SERVER_PARAM); |
161 |
} |
162 |
if (usernameAttr == null) { |
163 |
String authErrorUrl = "authenticationError.jsp"; |
164 |
authErrorUrl += "?name=" + (nameAttr==null? "-": nameAttr.toString()); |
165 |
authErrorUrl += "&givenname=" + (givennameAttr==null? "-": givennameAttr.toString()); |
166 |
authErrorUrl += "&sn=" + (snAttr==null? "-": snAttr.toString()); |
167 |
authErrorUrl += "&cn=" + (cnAttr==null? "-": cnAttr.toString()); |
168 |
authErrorUrl += "&mail=" + (mailAttr==null? "-": mailAttr.toString()); |
169 |
authErrorUrl += "&userclass=" + (userclassAttr==null? "-": userclassAttr.toString()); |
170 |
response.sendRedirect(authErrorUrl); |
171 |
return;
|
172 |
} |
173 |
String username = decodeAttribute(usernameAttr);
|
174 |
String name;
|
175 |
if (nameAttr != null && !nameAttr.toString().isEmpty()) |
176 |
name = decodeAttribute(nameAttr); |
177 |
else if (cnAttr != null && !cnAttr.toString().isEmpty()) { |
178 |
name = decodeAttribute(cnAttr); |
179 |
if (name.indexOf(';') != -1) |
180 |
name = name.substring(0, name.indexOf(';')); |
181 |
} else if (givennameAttr != null && snAttr != null && !givennameAttr.toString().isEmpty() && !snAttr.toString().isEmpty()) { |
182 |
String givenname = decodeAttribute(givennameAttr);
|
183 |
if (givenname.indexOf(';') != -1) |
184 |
givenname = givenname.substring(0, givenname.indexOf(';')); |
185 |
String sn = decodeAttribute(snAttr);
|
186 |
if (sn.indexOf(';') != -1) |
187 |
sn = sn.substring(0, sn.indexOf(';')); |
188 |
name = givenname + ' ' + sn;
|
189 |
} else if (givennameAttr == null && snAttr != null && !snAttr.toString().isEmpty()) { |
190 |
name = decodeAttribute(snAttr); |
191 |
if (name.indexOf(';') != -1) |
192 |
name = name.substring(0, name.indexOf(';')); |
193 |
} else
|
194 |
name = username; |
195 |
String mail = mailAttr != null ? mailAttr.toString() : username; |
196 |
if (mail.indexOf(';') != -1) |
197 |
mail = mail.substring(0, mail.indexOf(';')); |
198 |
// XXX we are not using the user class currently
|
199 |
String userclass = userclassAttr != null ? userclassAttr.toString() : ""; |
200 |
if (userclass.indexOf(';') != -1) |
201 |
userclass = userclass.substring(0, userclass.indexOf(';')); |
202 |
String persistentId = persistentIdAttr != null ? persistentIdAttr.toString() : ""; |
203 |
String idp = ""; |
204 |
String idpid = ""; |
205 |
if (!persistentId.isEmpty()) {
|
206 |
int bang = persistentId.indexOf('!'); |
207 |
if (bang > -1) { |
208 |
idp = persistentId.substring(0, bang);
|
209 |
idpid = persistentId.substring(bang + 1);
|
210 |
} |
211 |
} |
212 |
try {
|
213 |
user = getService().findUser(username); |
214 |
if (user == null) |
215 |
user = getService().createUser(username, name, mail, idp, idpid); |
216 |
if (!user.hasAcceptedPolicy()) {
|
217 |
String policyUrl = "policy.jsp"; |
218 |
if (request.getQueryString() != null) |
219 |
policyUrl += "?user=" + username + "&" + request.getQueryString(); |
220 |
response.sendRedirect(policyUrl); |
221 |
return;
|
222 |
} |
223 |
user.setName(name); |
224 |
user.setEmail(mail); |
225 |
user.setIdentityProvider(idp); |
226 |
user.setIdentityProviderId(idpid); |
227 |
user.setLastLogin(new Date()); |
228 |
if (user.getAuthToken() == null) |
229 |
user = getService().updateUserToken(user.getId()); |
230 |
// Set WebDAV password to token if it's never been set.
|
231 |
if (user.getWebDAVPassword() == null || user.getWebDAVPassword().length() == 0) { |
232 |
String tokenEncoded = new String(Base64.encodeBase64(user.getAuthToken()), "US-ASCII"); |
233 |
user.setWebDAVPassword(tokenEncoded); |
234 |
} |
235 |
getService().updateUser(user); |
236 |
} catch (RpcException e) {
|
237 |
String error = "An error occurred while communicating with the service"; |
238 |
logger.error(error, e); |
239 |
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error); |
240 |
return;
|
241 |
} catch (DuplicateNameException e) {
|
242 |
String error = "User with username " + username + " already exists"; |
243 |
logger.error(error, e); |
244 |
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error); |
245 |
return;
|
246 |
} catch (ObjectNotFoundException e) {
|
247 |
String error = "No username was provided"; |
248 |
logger.error(error, e); |
249 |
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error); |
250 |
return;
|
251 |
} |
252 |
String tokenEncoded = new String(Base64.encodeBase64(user.getAuthToken()), "US-ASCII"); |
253 |
String userEncoded = URLEncoder.encode(user.getUsername(), "US-ASCII"); |
254 |
if (logger.isDebugEnabled())
|
255 |
logger.debug("user: "+userEncoded+" token: "+tokenEncoded); |
256 |
if (nextUrl != null && !nextUrl.isEmpty()) { |
257 |
URI next;
|
258 |
if (gwtServer != null) |
259 |
nextUrl += '?' + GWT_SERVER_PARAM + '=' + gwtServer; |
260 |
try {
|
261 |
next = new URI(nextUrl); |
262 |
} catch (URISyntaxException e) { |
263 |
response.sendError(HttpServletResponse.SC_BAD_REQUEST, e.getMessage()); |
264 |
return;
|
265 |
} |
266 |
if ("x-gr-ebs-igss".equalsIgnoreCase(next.getScheme())) |
267 |
nextUrl += "?u=" + userEncoded + "&t=" + tokenEncoded; |
268 |
else {
|
269 |
String domain = next.getHost();
|
270 |
String path = next.getPath();
|
271 |
Cookie cookie = new Cookie(AUTH_COOKIE, userEncoded + COOKIE_SEPARATOR +
|
272 |
tokenEncoded); |
273 |
cookie.setMaxAge(-1);
|
274 |
cookie.setDomain(domain); |
275 |
cookie.setPath(path); |
276 |
response.addCookie(cookie); |
277 |
cookie = new Cookie(WEBDAV_COOKIE, user.getWebDAVPassword());
|
278 |
cookie.setMaxAge(-1);
|
279 |
cookie.setDomain(domain); |
280 |
cookie.setPath(path); |
281 |
response.addCookie(cookie); |
282 |
} |
283 |
response.sendRedirect(nextUrl); |
284 |
} else if (nonce != null) { |
285 |
nonce = URLEncoder.encode(nonce, "US-ASCII"); |
286 |
Nonce n = null;
|
287 |
try {
|
288 |
if (logger.isDebugEnabled())
|
289 |
logger.debug("user: "+user.getId()+" nonce: "+nonce); |
290 |
n = getService().getNonce(nonce, user.getId()); |
291 |
} catch (ObjectNotFoundException e) {
|
292 |
PrintWriter out = response.getWriter();
|
293 |
out.println("<HTML>");
|
294 |
out.println("<HEAD><TITLE>" + getServiceName() + " Authentication</TITLE>" + |
295 |
"<LINK TYPE='text/css' REL='stylesheet' HREF='gss.css'></HEAD>");
|
296 |
out.println("<BODY><CENTER><P>");
|
297 |
out.println("The supplied nonce could not be found!");
|
298 |
out.println("</CENTER></BODY></HTML>");
|
299 |
return;
|
300 |
} catch (RpcException e) {
|
301 |
String error = "An error occurred while communicating with the service"; |
302 |
logger.error(error, e); |
303 |
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error); |
304 |
return;
|
305 |
} |
306 |
try {
|
307 |
getService().activateUserNonce(user.getId(), nonce, n.getNonceExpiryDate()); |
308 |
} catch (ObjectNotFoundException e) {
|
309 |
String error = "Unable to find user"; |
310 |
logger.error(error, e); |
311 |
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error); |
312 |
return;
|
313 |
} catch (RpcException e) {
|
314 |
String error = "An error occurred while communicating with the service"; |
315 |
logger.error(error, e); |
316 |
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, error); |
317 |
return;
|
318 |
} |
319 |
try {
|
320 |
getService().removeNonce(n.getId()); |
321 |
} catch (ObjectNotFoundException e) {
|
322 |
logger.info("Nonce already removed!", e);
|
323 |
} catch (RpcException e) {
|
324 |
logger.warn("Could not remove nonce from data store", e);
|
325 |
} |
326 |
PrintWriter out = response.getWriter();
|
327 |
out.println("<HTML>");
|
328 |
out.println("<HEAD><TITLE>" + getServiceName() + " Authentication</TITLE>" + |
329 |
"<LINK TYPE='text/css' REL='stylesheet' HREF='gss.css'></HEAD>");
|
330 |
out.println("<BODY><CENTER><P>");
|
331 |
out.println("You can now close this browser window and return to your application.");
|
332 |
out.println("</CENTER></BODY></HTML>");
|
333 |
} else {
|
334 |
PrintWriter out = response.getWriter();
|
335 |
out.println("<HTML>");
|
336 |
out.println("<HEAD><TITLE>" + getServiceName() + " Authentication</TITLE>" + |
337 |
"<LINK TYPE='text/css' REL='stylesheet' HREF='gss.css'></HEAD>");
|
338 |
out.println("<BODY><CENTER><P>");
|
339 |
out.println("Name: " + user.getName() + "<BR>"); |
340 |
out.println("E-mail: " + user.getEmail() + "<BR><P>"); |
341 |
out.println("Username: " + user.getUsername() + "<BR>"); |
342 |
out.println("Athentication token: " + tokenEncoded + "<BR>"); |
343 |
out.println("</CENTER></BODY></HTML>");
|
344 |
} |
345 |
} |
346 |
|
347 |
/**
|
348 |
* Decode the request attribute provided by the container to a UTF-8
|
349 |
* string, since GSS assumes all data to be encoded in UTF-8. The
|
350 |
* servlet container's encoding can be specified in gss.properties.
|
351 |
*/
|
352 |
private String decodeAttribute(Object attribute) throws UnsupportedEncodingException { |
353 |
return new String(attribute.toString().getBytes(getConfiguration().getString("requestAttributeEncoding")), "UTF-8"); |
354 |
} |
355 |
|
356 |
/**
|
357 |
* A helper method that converts a byte buffer to a printable list of
|
358 |
* hexadecimal numbers.
|
359 |
*/
|
360 |
private String getHexString(byte[] buffer) { |
361 |
StringBuilder sb = new StringBuilder(); |
362 |
Formatter formatter = new Formatter(sb); |
363 |
for (int i=0; i<buffer.length; i++) |
364 |
formatter.format("0x%x, ", buffer[i]);
|
365 |
return sb.toString();
|
366 |
} |
367 |
|
368 |
} |