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