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