Statistics
| Branch: | Tag: | Revision:

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
}