Statistics
| Branch: | Tag: | Revision:

root / src / gr / ebs / gss / server / rest / RequestHandler.java @ f24cdf08

History | View | Annotate | Download (24.5 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.rest;
20

    
21
import static gr.ebs.gss.server.configuration.GSSConfigurationFactory.getConfiguration;
22
import gr.ebs.gss.client.exceptions.InsufficientPermissionsException;
23
import gr.ebs.gss.client.exceptions.ObjectNotFoundException;
24
import gr.ebs.gss.client.exceptions.RpcException;
25
import gr.ebs.gss.server.domain.User;
26
import gr.ebs.gss.server.domain.dto.FileHeaderDTO;
27
import gr.ebs.gss.server.webdav.Webdav;
28

    
29
import java.io.ByteArrayInputStream;
30
import java.io.ByteArrayOutputStream;
31
import java.io.IOException;
32
import java.io.OutputStreamWriter;
33
import java.io.PrintWriter;
34
import java.io.UnsupportedEncodingException;
35
import java.util.Calendar;
36
import java.util.Enumeration;
37
import java.util.HashMap;
38
import java.util.Map;
39

    
40
import javax.crypto.Mac;
41
import javax.crypto.spec.SecretKeySpec;
42
import javax.servlet.ServletException;
43
import javax.servlet.http.HttpServletRequest;
44
import javax.servlet.http.HttpServletResponse;
45

    
46
import org.apache.commons.codec.binary.Base64;
47
import org.apache.commons.logging.Log;
48
import org.apache.commons.logging.LogFactory;
49

    
50
/**
51
 * The servlet that handles requests for the REST API.
52
 *
53
 * @author past
54
 */
55
public class RequestHandler extends Webdav {
56
        /**
57
         * The request attribute containing the flag that will be used to indicate an
58
         * authentication bypass has occurred. We will have to check for authentication
59
         * later. This is a shortcut for dealing with publicly-readable files.
60
         */
61
        protected static final String AUTH_DEFERRED_ATTR = "authDeferred";
62

    
63
        /**
64
         * The path for the search subsystem.
65
         */
66
        protected static final String PATH_SEARCH = "/search";
67

    
68
        /**
69
         * The path for the user search subsystem.
70
         */
71
        protected static final String PATH_USERS = "/users";
72

    
73
        /**
74
         * The path for the resource manipulation subsystem.
75
         */
76
        protected static final String PATH_FILES = FileHeaderDTO.PATH_FILES;
77

    
78
        /**
79
         * The path for the trash virtual folder.
80
         */
81
        protected static final String PATH_TRASH = "/trash";
82

    
83
        /**
84
         * The path for the subsystem that deals with the user attributes.
85
         */
86
        protected static final String PATH_GROUPS = "/groups";
87

    
88
        /**
89
         * The path for the shared resources virtual folder.
90
         */
91
        protected static final String PATH_SHARED = "/shared";
92

    
93
        /**
94
         * The path for the other users' shared resources virtual folder.
95
         */
96
        protected static final String PATH_OTHERS = "/others";
97

    
98
        /**
99
         * The path for tags created by the user.
100
         */
101
        protected static final String PATH_TAGS = "/tags";
102

    
103
        /**
104
         * The GSS-specific header for the request timestamp.
105
         */
106
        private static final String GSS_DATE_HEADER = "X-GSS-Date";
107

    
108
        /**
109
         * The RFC 2616 date header.
110
         */
111
        private static final String DATE_HEADER = "Date";
112

    
113
        /**
114
         * The Authorization HTTP header.
115
         */
116
        private static final String AUTHORIZATION_HEADER = "Authorization";
117

    
118
        /**
119
         * The group parameter name.
120
         */
121
        protected static final String GROUP_PARAMETER = "name";
122

    
123
        /**
124
         * The username parameter name.
125
         */
126
        protected static final String USERNAME_PARAMETER = "name";
127

    
128
        /**
129
         * The "new folder name" parameter name.
130
         */
131
        protected static final String NEW_FOLDER_PARAMETER = "new";
132

    
133
        /**
134
         * The resource update parameter name.
135
         */
136
        protected static final String RESOURCE_UPDATE_PARAMETER = "update";
137

    
138
        /**
139
         * The resource trash parameter name.
140
         */
141
        protected static final String RESOURCE_TRASH_PARAMETER = "trash";
142

    
143
        /**
144
         * The resource restore parameter name.
145
         */
146
        protected static final String RESOURCE_RESTORE_PARAMETER = "restore";
147

    
148
        /**
149
         * The resource copy parameter name.
150
         */
151
        protected static final String RESOURCE_COPY_PARAMETER = "copy";
152

    
153
        /**
154
         * The resource move parameter name.
155
         */
156
        protected static final String RESOURCE_MOVE_PARAMETER = "move";
157

    
158
        /**
159
         * The HMAC-SHA1 hash name.
160
         */
161
        private static final String HMAC_SHA1 = "HmacSHA1";
162

    
163
        /**
164
         * The serial version UID of the class.
165
         */
166
        private static final long serialVersionUID = 1L;
167

    
168
        /**
169
         * The logger.
170
         */
171
        private static Log logger = LogFactory.getLog(RequestHandler.class);
172

    
173
        /**
174
         * Create a mapping between paths and allowed HTTP methods for fast lookup.
175
         */
176
        private final Map<String, String> methodsAllowed = new HashMap<String, String>(7);
177

    
178
        @Override
179
        public void init() throws ServletException {
180
                super.init();
181
                methodsAllowed.put(PATH_FILES, METHOD_GET + ", " + METHOD_POST +
182
                                        ", " + METHOD_DELETE + ", " + METHOD_PUT + ", " + METHOD_HEAD);
183
                methodsAllowed.put(PATH_GROUPS, METHOD_GET + ", " + METHOD_POST +
184
                                        ", " + METHOD_DELETE);
185
                methodsAllowed.put(PATH_OTHERS, METHOD_GET);
186
                methodsAllowed.put(PATH_SEARCH, METHOD_GET);
187
                methodsAllowed.put(PATH_USERS, METHOD_GET);
188
                methodsAllowed.put(PATH_SHARED, METHOD_GET);
189
                methodsAllowed.put(PATH_TAGS, METHOD_GET);
190
                methodsAllowed.put(PATH_TRASH, METHOD_GET + ", " + METHOD_DELETE);
191
        }
192

    
193
        /**
194
         * Return the root of every API request URL.
195
         */
196
        protected String getApiRoot() {
197
                return getConfiguration().getString("restUrl", "http://localhost:8080/gss/rest/");
198
        }
199

    
200
        @Override
201
        public void service(final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException {
202
        String method = request.getMethod();
203
                String path = getRelativePath(request);
204

    
205
                if (logger.isDebugEnabled())
206
            logger.debug("[" + method + "] " + path);
207

    
208
                if (!isRequestValid(request)) {
209
                        if (!method.equals(METHOD_GET) && !method.equals(METHOD_HEAD) &&
210
                                                !method.equals(METHOD_POST)) {
211
                                response.sendError(HttpServletResponse.SC_FORBIDDEN);
212
                                return;
213
                        }
214
                        // Raise a flag to indicate we will have to check for
215
                        // authentication later. This is a shortcut for dealing
216
                        // with publicly-readable files.
217
                        request.setAttribute(AUTH_DEFERRED_ATTR, true);
218
                }
219

    
220
                // Dispatch to the appropriate method handler.
221
                if (method.equals(METHOD_GET))
222
                        doGet(request, response);
223
                else if (method.equals(METHOD_POST))
224
                        doPost(request, response);
225
                else if (method.equals(METHOD_PUT))
226
                        doPut(request, response);
227
                else if (method.equals(METHOD_DELETE))
228
                        doDelete(request, response);
229
                else if (method.equals(METHOD_HEAD))
230
                        doHead(request, response);
231
                else
232
                        response.sendError(HttpServletResponse.SC_BAD_REQUEST);
233
        }
234

    
235
        @Override
236
        protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
237
                boolean authDeferred = getAuthDeferred(req);
238
            // Strip the username part
239
            String path;
240
                try {
241
                        path = getUserPath(req);
242
                } catch (ObjectNotFoundException e) {
243
                        if (authDeferred) {
244
                                // We do not want to leak information if the request
245
                                // was not authenticated.
246
                                resp.sendError(HttpServletResponse.SC_FORBIDDEN);
247
                                return;
248
                        }
249
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
250
                        return;
251
                }
252
                if (authDeferred && !path.startsWith(PATH_FILES)) {
253
                        // Only files may be open to the public.
254
                        resp.sendError(HttpServletResponse.SC_FORBIDDEN);
255
                        return;
256
                }
257

    
258
                if (path.startsWith(PATH_GROUPS)) {
259
            resp.addHeader("Allow", methodsAllowed.get(PATH_GROUPS));
260
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
261
                } else if (path.startsWith(PATH_OTHERS)) {
262
            resp.addHeader("Allow", methodsAllowed.get(PATH_OTHERS));
263
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
264
                } else if (path.startsWith(PATH_SEARCH)) {
265
            resp.addHeader("Allow", methodsAllowed.get(PATH_SEARCH));
266
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
267
                } else if (path.startsWith(PATH_USERS)) {
268
            resp.addHeader("Allow", methodsAllowed.get(PATH_USERS));
269
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
270
                } else if (path.startsWith(PATH_SHARED)) {
271
            resp.addHeader("Allow", methodsAllowed.get(PATH_SHARED));
272
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
273
                } else if (path.startsWith(PATH_TAGS)) {
274
            resp.addHeader("Allow", methodsAllowed.get(PATH_TAGS));
275
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
276
                } else if (path.startsWith(PATH_TRASH)) {
277
            resp.addHeader("Allow", methodsAllowed.get(PATH_TRASH));
278
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
279
                } else if (path.startsWith(PATH_FILES))
280
                        // Serve the requested resource, without the data content
281
                        new FilesHandler(getServletContext()).serveResource(req, resp, false);
282
                else
283
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
284
        }
285

    
286
        /**
287
         * Handle storing and updating file resources.
288
         *
289
     * @param req The servlet request we are processing
290
     * @param resp The servlet response we are creating
291
         * @throws IOException if the response cannot be sent
292
         */
293
        @Override
294
        protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException {
295
                // TODO: fix code duplication between doPut() and Webdav.doPut()
296
            // Strip the username part
297
            String path;
298
                try {
299
                        path = getUserPath(req);
300
                } catch (ObjectNotFoundException e) {
301
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
302
                        return;
303
                }
304

    
305
            if (path.startsWith(PATH_GROUPS)) {
306
            resp.addHeader("Allow", methodsAllowed.get(PATH_GROUPS));
307
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
308
                } else if (path.startsWith(PATH_OTHERS)) {
309
            resp.addHeader("Allow", methodsAllowed.get(PATH_OTHERS));
310
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
311
                } else if (path.startsWith(PATH_SEARCH)) {
312
            resp.addHeader("Allow", methodsAllowed.get(PATH_SEARCH));
313
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
314
                } else if (path.startsWith(PATH_USERS)) {
315
            resp.addHeader("Allow", methodsAllowed.get(PATH_USERS));
316
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
317
                } else if (path.startsWith(PATH_SHARED)) {
318
            resp.addHeader("Allow", methodsAllowed.get(PATH_SHARED));
319
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
320
                } else if (path.startsWith(PATH_TAGS)) {
321
            resp.addHeader("Allow", methodsAllowed.get(PATH_TAGS));
322
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
323
                } else if (path.startsWith(PATH_TRASH)) {
324
            resp.addHeader("Allow", methodsAllowed.get(PATH_TRASH));
325
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
326
                } else if (path.startsWith(PATH_FILES))
327
                        new FilesHandler(getServletContext()).putResource(req, resp);
328
                else
329
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
330
        }
331

    
332
        @Override
333
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
334
                boolean authDeferred = getAuthDeferred(req);
335
            // Strip the username part
336
            String path;
337
                try {
338
                        path = getUserPath(req);
339
                } catch (ObjectNotFoundException e) {
340
                        if (authDeferred) {
341
                                // We do not want to leak information if the request
342
                                // was not authenticated.
343
                                resp.sendError(HttpServletResponse.SC_FORBIDDEN);
344
                                return;
345
                        }
346
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
347
                        return;
348
                }
349
                if (authDeferred && !path.startsWith(PATH_FILES)) {
350
                        // Only files may be open to the public.
351
                        resp.sendError(HttpServletResponse.SC_FORBIDDEN);
352
                        return;
353
                }
354

    
355
            // Dispatch according to the specified namespace
356
            if (path.equals("") || path.equals("/"))
357
                        new UserHandler().serveUser(req, resp);
358
                else if (path.startsWith(PATH_FILES))
359
                        // Serve the requested resource, including the data content
360
                           new FilesHandler(getServletContext()).serveResource(req, resp, true);
361
                else if (path.startsWith(PATH_TRASH))
362
                        new TrashHandler().serveTrash(req, resp);
363
                else if (path.startsWith(PATH_SEARCH))
364
                        new SearchHandler().serveSearchResults(req, resp);
365
                else if (path.startsWith(PATH_USERS))
366
                        new UserSearchHandler().serveResults(req, resp);
367
                else if (path.startsWith(PATH_GROUPS))
368
                        new GroupsHandler().serveGroups(req, resp);
369
                else if (path.startsWith(PATH_SHARED))
370
                        new SharedHandler().serveShared(req, resp);
371
                else if (path.startsWith(PATH_OTHERS))
372
                        new OthersHandler().serveOthers(req, resp);
373
                else if (path.startsWith(PATH_TAGS))
374
                        new TagsHandler().serveTags(req, resp);
375
                else
376
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
377
        }
378

    
379
    /**
380
     * Handle a Delete request.
381
     *
382
     * @param req The servlet request we are processing
383
     * @param resp The servlet response we are processing
384
         * @throws IOException if the response cannot be sent
385
     */
386
    @Override
387
        protected void doDelete(HttpServletRequest req, HttpServletResponse resp)
388
                    throws IOException {
389
            // Strip the username part
390
            String path;
391
                try {
392
                        path = getUserPath(req);
393
                } catch (ObjectNotFoundException e) {
394
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
395
                        return;
396
                }
397

    
398
                if (path.startsWith(PATH_OTHERS)) {
399
            resp.addHeader("Allow", methodsAllowed.get(PATH_OTHERS));
400
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
401
                } else if (path.startsWith(PATH_SEARCH)) {
402
            resp.addHeader("Allow", methodsAllowed.get(PATH_SEARCH));
403
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
404
                } else if (path.startsWith(PATH_USERS)) {
405
            resp.addHeader("Allow", methodsAllowed.get(PATH_USERS));
406
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
407
                } else if (path.startsWith(PATH_SHARED)) {
408
            resp.addHeader("Allow", methodsAllowed.get(PATH_SHARED));
409
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
410
                } else if (path.startsWith(PATH_TAGS)) {
411
            resp.addHeader("Allow", methodsAllowed.get(PATH_TAGS));
412
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
413
                } else if (path.startsWith(PATH_GROUPS))
414
                        new GroupsHandler().deleteGroup(req, resp);
415
                else if (path.startsWith(PATH_TRASH))
416
                        new TrashHandler().emptyTrash(req, resp);
417
                else if (path.startsWith(PATH_FILES))
418
                        new FilesHandler(getServletContext()).deleteResource(req, resp);
419
                else
420
                    resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
421
    }
422

    
423
        @Override
424
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
425
                boolean authDeferred = getAuthDeferred(req);
426
            // Strip the username part
427
            String path;
428
                try {
429
                        path = getUserPath(req);
430
                } catch (ObjectNotFoundException e) {
431
                        if (authDeferred) {
432
                                // We do not want to leak information if the request
433
                                // was not authenticated.
434
                                resp.sendError(HttpServletResponse.SC_FORBIDDEN);
435
                                return;
436
                        }
437
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
438
                        return;
439
                }
440
                if (authDeferred && !path.startsWith(PATH_FILES)) {
441
                        // Only POST to files may be authenticated without an Authorization header.
442
                        resp.sendError(HttpServletResponse.SC_FORBIDDEN);
443
                        return;
444
                }
445

    
446
                if (path.startsWith(PATH_OTHERS)) {
447
            resp.addHeader("Allow", methodsAllowed.get(PATH_OTHERS));
448
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
449
                } else if (path.startsWith(PATH_SEARCH)) {
450
            resp.addHeader("Allow", methodsAllowed.get(PATH_SEARCH));
451
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
452
                } else if (path.startsWith(PATH_USERS)) {
453
                        resp.addHeader("Allow", methodsAllowed.get(PATH_USERS));
454
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
455
                } else if (path.startsWith(PATH_SHARED)) {
456
            resp.addHeader("Allow", methodsAllowed.get(PATH_SHARED));
457
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
458
                } else if (path.startsWith(PATH_TAGS)) {
459
            resp.addHeader("Allow", methodsAllowed.get(PATH_TAGS));
460
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
461
                } else if (path.startsWith(PATH_GROUPS))
462
                        new GroupsHandler().postGroup(req, resp);
463
                else if (path.startsWith(PATH_TRASH)) {
464
            resp.addHeader("Allow", methodsAllowed.get(PATH_TRASH));
465
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
466
                } else if (path.startsWith(PATH_FILES))
467
                        new FilesHandler(getServletContext()).postResource(req, resp);
468
                else if (path.equals("/"))
469
                        new UserHandler().postUser(req, resp);
470
                else
471
                    resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
472
        }
473

    
474
        /**
475
         * Return the path inside the user namespace.
476
         *
477
         * @param req the HTTP request
478
         * @return the path after the username part has been removed
479
         * @throws ObjectNotFoundException if the namespace owner was not found
480
         */
481
        private String getUserPath(HttpServletRequest req) throws ObjectNotFoundException {
482
        String path = getRelativePath(req);
483
                if (path.length() < 2)
484
                        return path;
485
                int slash = path.substring(1).indexOf('/');
486
                if (slash == -1)
487
                        return path;
488
                String owner = path.substring(1, slash + 1);
489
                User o;
490
                try {
491
                        o = getService().findUser(owner);
492
                } catch (RpcException e) {
493
                        logger.error("", e);
494
                        throw new ObjectNotFoundException("User " + owner + " not found, due to internal server error");
495
                }
496
                if (o != null) {
497
                        req.setAttribute(OWNER_ATTRIBUTE, o);
498
                        return path.substring(slash + 1);
499
                }
500
                if (!path.startsWith(PATH_SEARCH) && !path.startsWith(PATH_USERS))
501
                        throw new ObjectNotFoundException("User " + owner + " not found");
502
                return path;
503
        }
504

    
505
        /**
506
         * Retrieve the request context path with or without a trailing slash
507
         * according to the provided argument.
508
         *
509
         * @param req the HTTP request
510
         * @param withTrailingSlash a flag that denotes whether the path should
511
         *                         end with a slash
512
         * @return the context path
513
         */
514
        protected String getContextPath(HttpServletRequest req, boolean withTrailingSlash) {
515
                String contextPath = req.getRequestURL().toString();
516
                if (withTrailingSlash)
517
                        return contextPath.endsWith("/")? contextPath: contextPath + '/';
518
                return contextPath.endsWith("/")? contextPath.substring(0, contextPath.length()-1): contextPath;
519

    
520
        }
521

    
522
        /**
523
         * @param req
524
         * @param resp
525
         * @param json
526
         * @throws UnsupportedEncodingException
527
         * @throws IOException
528
         */
529
        protected void sendJson(HttpServletRequest req, HttpServletResponse resp, String json) throws UnsupportedEncodingException, IOException {
530
                ByteArrayOutputStream stream = new ByteArrayOutputStream();
531
            OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
532
            PrintWriter writer = new PrintWriter(osWriter);
533

    
534
            writer.write(json);
535
            writer.flush();
536

    
537
            resp.setContentType("application/json;charset=UTF-8");
538
            resp.setBufferSize(output);
539
                try {
540
                        copy(null, new ByteArrayInputStream(stream.toByteArray()), resp.getOutputStream(), req, null);
541
                } catch (ObjectNotFoundException e) {
542
                        // This should never happen with a null first parameter.
543
                        logger.error("", e);
544
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
545
                        return;
546
                } catch (InsufficientPermissionsException e) {
547
                        // This should never happen with a null first parameter.
548
                        logger.error("", e);
549
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
550
                        return;
551
                } catch (RpcException e) {
552
                        logger.error("", e);
553
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
554
                        return;
555
                }
556
        }
557

    
558
        /**
559
         * Retrieve the path to the requested resource after removing the user namespace
560
         * part and the subsequent namespace part that differentiates resources like files,
561
         * groups, trash, etc.
562
         *
563
         * @param req the HTTP request
564
         * @param namespace the subnamespace
565
         * @return the inner path
566
         */
567
        protected String getInnerPath(HttpServletRequest req, String namespace) {
568
            // Strip the username part
569
            String path;
570
                try {
571
                        path = getUserPath(req);
572
                } catch (ObjectNotFoundException e) {
573
                        throw new RuntimeException(e.getMessage());
574
                }
575
                // Chop the resource namespace part
576
            path = path.substring(namespace.length());
577
                return path;
578
        }
579

    
580
        /**
581
         * Confirms the validity of the request.
582
         *
583
         * @param request the incoming HTTP request
584
         * @return true if the request is valid, false otherwise
585
         */
586
        private boolean isRequestValid(HttpServletRequest request) {
587
                if (logger.isDebugEnabled()) {
588
                        Enumeration headers = request.getHeaderNames();
589
                        while (headers.hasMoreElements()) {
590
                                String h = (String) headers.nextElement();
591
                                        logger.debug(h + ": " +        request.getHeader(h));
592
                        }
593
                }
594
                // Fetch the timestamp used to guard against replay attacks.
595
                long timestamp = 0;
596
                boolean useGssDateHeader = true;
597
                try {
598
                        timestamp = request.getDateHeader(GSS_DATE_HEADER);
599
                        if (timestamp == -1) {
600
                                useGssDateHeader = false;
601
                                timestamp = request.getDateHeader(DATE_HEADER);
602
                        }
603
                } catch (IllegalArgumentException e) {
604
                        return false;
605
                }
606
                if (!isTimeValid(timestamp))
607
                        return false;
608

    
609
                // Fetch the Authorization header and find the user specified in it.
610
                String auth = request.getHeader(AUTHORIZATION_HEADER);
611
                if (auth == null)
612
                        return false;
613
                String[] authParts = auth.split(" ");
614
                if (authParts.length != 2)
615
                        return false;
616
                String username = authParts[0];
617
                String signature = authParts[1];
618
                User user = null;
619
                try {
620
                        user = getService().findUser(username);
621
                } catch (RpcException e) {
622
                        return false;
623
                }
624
                if (user == null)
625
                        return false;
626

    
627
                request.setAttribute(USER_ATTRIBUTE, user);
628

    
629
                // Validate the signature in the Authorization header.
630
                String dateHeader = useGssDateHeader? request.getHeader(GSS_DATE_HEADER):
631
                        request.getHeader(DATE_HEADER);
632
                String data;
633
                // Remove the servlet path from the request URI.
634
                String p = request.getRequestURI();
635
                String servletPath = request.getContextPath() + request.getServletPath();
636
                p = p.substring(servletPath.length());
637
                data = request.getMethod() + dateHeader + p;
638
                return isSignatureValid(signature, user, data);
639
        }
640

    
641
        /**
642
         * Calculates the signature for the specified data String and then
643
         * compares it against the provided signature. If the signatures match,
644
         * the method returns true. Otherwise it returns false.
645
         *
646
         * @param signature the signature to compare against
647
         * @param user the current user
648
         * @param data the data to sign
649
         * @return true if the calculated signature matches the supplied one
650
         */
651
        protected boolean isSignatureValid(String signature, User user, String data) {
652
                if (logger.isDebugEnabled())
653
                        logger.debug("server pre-signing data: "+data);
654
                String serverSignature = null;
655
                // If the authentication token is not valid, the user must get another one.
656
                if (user.getAuthToken() == null)
657
                        return false;
658
                // Get an HMAC-SHA1 key from the authentication token.
659
                SecretKeySpec signingKey = new SecretKeySpec(user.getAuthToken(), HMAC_SHA1);
660
                try {
661
                        // Get an HMAC-SHA1 Mac instance and initialize with the signing key.
662
                        Mac mac = Mac.getInstance(HMAC_SHA1);
663
                        mac.init(signingKey);
664
                        // Compute the HMAC on input data bytes.
665
                        byte[] rawHmac = mac.doFinal(data.getBytes());
666
                        serverSignature = new String(Base64.encodeBase64(rawHmac), "US-ASCII");
667
                } catch (Exception e) {
668
                        logger.error("Error while creating signature", e);
669
                        return false;
670
                }
671

    
672
                if (logger.isDebugEnabled())
673
                        logger.debug("Signature: client="+signature+", server="+serverSignature);
674
                if (!serverSignature.equals(signature))
675
                        return false;
676

    
677
                return true;
678
        }
679

    
680
        /**
681
         * A helper method that checks if the timestamp of the request
682
         * is within TIME_SKEW milliseconds of the current time. If
683
         * the timestamp is older (or even newer) than that, it is
684
         * considered invalid.
685
         *
686
         * @param timestamp the time of the request
687
         * @return true if the timestamp is valid
688
         */
689
        protected boolean isTimeValid(long timestamp) {
690
                if (timestamp == -1)
691
                        return false;
692
                Calendar cal = Calendar.getInstance();
693
                if (logger.isDebugEnabled())
694
                        logger.debug("Time: server=" + cal.getTimeInMillis() + ", client=" + timestamp);
695
                // Ignore the request if the timestamp is too far off.
696
                if (Math.abs(timestamp - cal.getTimeInMillis()) > getConfiguration().getInt("timeSkew", 600000))
697
                        return false;
698
                return true;
699
        }
700

    
701
        protected boolean getAuthDeferred(HttpServletRequest req) {
702
                Boolean attr = (Boolean) req.getAttribute(AUTH_DEFERRED_ATTR);
703
                return attr == null? false: attr;
704
        }
705

    
706
        /**
707
         * Return the actual requested path in the API namespace.
708
         *
709
         * @param request the servlet request we are processing
710
         * @return the relative path
711
         */
712
        @Override
713
        protected String getRelativePath(HttpServletRequest request) {
714
                // Remove the servlet path from the request URI.
715
                String p = request.getRequestURI();
716
                String servletPath = request.getContextPath() + request.getServletPath();
717
                String result = p.substring(servletPath.length());
718
                if (result == null || result.equals(""))
719
                        result = "/";
720
                return result;
721

    
722
        }
723
}