Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (25.7 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 path for token renewal.
105
         */
106
        protected static final String PATH_TOKEN = "/newtoken";
107

    
108
        /**
109
         * The GSS-specific header for the request timestamp.
110
         */
111
        protected static final String GSS_DATE_HEADER = "X-GSS-Date";
112

    
113
        /**
114
         * The RFC 2616 date header.
115
         */
116
        protected static final String DATE_HEADER = "Date";
117

    
118
        /**
119
         * The Authorization HTTP header.
120
         */
121
        protected static final String AUTHORIZATION_HEADER = "Authorization";
122

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

    
128
        /**
129
         * The username parameter name.
130
         */
131
        protected static final String USERNAME_PARAMETER = "name";
132

    
133
        /**
134
         * The "new folder name" parameter name.
135
         */
136
        protected static final String NEW_FOLDER_PARAMETER = "new";
137

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

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

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

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

    
158
        /**
159
         * The resource move parameter name.
160
         */
161
        protected static final String RESOURCE_MOVE_PARAMETER = "move";
162

    
163
        /**
164
         * The HMAC-SHA1 hash name.
165
         */
166
        private static final String HMAC_SHA1 = "HmacSHA1";
167

    
168
        /**
169
         * The serial version UID of the class.
170
         */
171
        private static final long serialVersionUID = 1L;
172

    
173
        /**
174
         * The logger.
175
         */
176
        private static Log logger = LogFactory.getLog(RequestHandler.class);
177

    
178
        /**
179
         * Create a mapping between paths and allowed HTTP methods for fast lookup.
180
         */
181
        private final Map<String, String> methodsAllowed = new HashMap<String, String>(7);
182

    
183
        @Override
184
        public void init() throws ServletException {
185
                super.init();
186
                methodsAllowed.put(PATH_FILES, METHOD_GET + ", " + METHOD_POST +
187
                                        ", " + METHOD_DELETE + ", " + METHOD_PUT + ", " + METHOD_HEAD);
188
                methodsAllowed.put(PATH_GROUPS, METHOD_GET + ", " + METHOD_POST +
189
                                        ", " + METHOD_DELETE);
190
                methodsAllowed.put(PATH_OTHERS, METHOD_GET);
191
                methodsAllowed.put(PATH_SEARCH, METHOD_GET);
192
                methodsAllowed.put(PATH_USERS, METHOD_GET);
193
                methodsAllowed.put(PATH_SHARED, METHOD_GET);
194
                methodsAllowed.put(PATH_TAGS, METHOD_GET);
195
                methodsAllowed.put(PATH_TRASH, METHOD_GET + ", " + METHOD_DELETE);
196
                methodsAllowed.put(PATH_TOKEN, METHOD_GET);
197
        }
198

    
199
        /**
200
         * Return the root of every API request URL.
201
         */
202
        protected String getApiRoot() {
203
                return getConfiguration().getString("restUrl", "http://localhost:8080/gss/rest/");
204
        }
205

    
206
        @Override
207
        public void service(final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException {
208
        String method = request.getMethod();
209
                String path = getRelativePath(request);
210

    
211
                if (logger.isDebugEnabled())
212
            logger.debug("[" + method + "] " + path);
213

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

    
226
                // Dispatch to the appropriate method handler.
227
                if (method.equals(METHOD_GET))
228
                        doGet(request, response);
229
                else if (method.equals(METHOD_POST))
230
                        doPost(request, response);
231
                else if (method.equals(METHOD_PUT))
232
                        doPut(request, response);
233
                else if (method.equals(METHOD_DELETE))
234
                        doDelete(request, response);
235
                else if (method.equals(METHOD_HEAD))
236
                        doHead(request, response);
237
                else
238
                        response.sendError(HttpServletResponse.SC_BAD_REQUEST);
239
        }
240

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

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

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

    
314
            if (path.startsWith(PATH_GROUPS)) {
315
            resp.addHeader("Allow", methodsAllowed.get(PATH_GROUPS));
316
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
317
                } else if (path.startsWith(PATH_OTHERS)) {
318
            resp.addHeader("Allow", methodsAllowed.get(PATH_OTHERS));
319
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
320
                } else if (path.startsWith(PATH_SEARCH)) {
321
            resp.addHeader("Allow", methodsAllowed.get(PATH_SEARCH));
322
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
323
                } else if (path.startsWith(PATH_TOKEN)) {
324
            resp.addHeader("Allow", methodsAllowed.get(PATH_TOKEN));
325
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
326
                } else if (path.startsWith(PATH_USERS)) {
327
            resp.addHeader("Allow", methodsAllowed.get(PATH_USERS));
328
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
329
                } else if (path.startsWith(PATH_SHARED)) {
330
            resp.addHeader("Allow", methodsAllowed.get(PATH_SHARED));
331
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
332
                } else if (path.startsWith(PATH_TAGS)) {
333
            resp.addHeader("Allow", methodsAllowed.get(PATH_TAGS));
334
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
335
                } else if (path.startsWith(PATH_TRASH)) {
336
            resp.addHeader("Allow", methodsAllowed.get(PATH_TRASH));
337
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
338
                } else if (path.startsWith(PATH_FILES))
339
                        new FilesHandler(getServletContext()).putResource(req, resp);
340
                else
341
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
342
        }
343

    
344
        @Override
345
        protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
346
                boolean authDeferred = getAuthDeferred(req);
347
            // Strip the username part
348
            String path;
349
                try {
350
                        path = getUserPath(req);
351
                } catch (ObjectNotFoundException e) {
352
                        if (authDeferred) {
353
                                // We do not want to leak information if the request
354
                                // was not authenticated.
355
                                resp.sendError(HttpServletResponse.SC_FORBIDDEN);
356
                                return;
357
                        }
358
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
359
                        return;
360
                }
361
                if (authDeferred && !path.startsWith(PATH_FILES)) {
362
                        // Only files may be open to the public.
363
                        resp.sendError(HttpServletResponse.SC_FORBIDDEN);
364
                        return;
365
                }
366

    
367
            // Dispatch according to the specified namespace
368
            if (path.equals("") || path.equals("/"))
369
                        new UserHandler().serveUser(req, resp);
370
                else if (path.startsWith(PATH_FILES))
371
                        // Serve the requested resource, including the data content
372
                           new FilesHandler(getServletContext()).serveResource(req, resp, true);
373
                else if (path.startsWith(PATH_TRASH))
374
                        new TrashHandler().serveTrash(req, resp);
375
                else if (path.startsWith(PATH_SEARCH))
376
                        new SearchHandler().serveSearchResults(req, resp);
377
                else if (path.startsWith(PATH_USERS))
378
                        new UserSearchHandler().serveResults(req, resp);
379
                else if (path.startsWith(PATH_GROUPS))
380
                        new GroupsHandler().serveGroups(req, resp);
381
                else if (path.startsWith(PATH_SHARED))
382
                        new SharedHandler().serveShared(req, resp);
383
                else if (path.startsWith(PATH_OTHERS))
384
                        new OthersHandler().serveOthers(req, resp);
385
                else if (path.startsWith(PATH_TAGS))
386
                        new TagsHandler().serveTags(req, resp);
387
                else if (path.startsWith(PATH_TOKEN))
388
                        new TokenHandler().newToken(req, resp);
389
                else
390
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
391
        }
392

    
393
    /**
394
     * Handle a Delete request.
395
     *
396
     * @param req The servlet request we are processing
397
     * @param resp The servlet response we are processing
398
         * @throws IOException if the response cannot be sent
399
     */
400
    @Override
401
        protected void doDelete(HttpServletRequest req, HttpServletResponse resp)
402
                    throws IOException {
403
            // Strip the username part
404
            String path;
405
                try {
406
                        path = getUserPath(req);
407
                } catch (ObjectNotFoundException e) {
408
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
409
                        return;
410
                }
411

    
412
                if (path.startsWith(PATH_OTHERS)) {
413
            resp.addHeader("Allow", methodsAllowed.get(PATH_OTHERS));
414
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
415
                } else if (path.startsWith(PATH_SEARCH)) {
416
            resp.addHeader("Allow", methodsAllowed.get(PATH_SEARCH));
417
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
418
                } else if (path.startsWith(PATH_TOKEN)) {
419
            resp.addHeader("Allow", methodsAllowed.get(PATH_TOKEN));
420
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
421
                } else if (path.startsWith(PATH_USERS)) {
422
            resp.addHeader("Allow", methodsAllowed.get(PATH_USERS));
423
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
424
                } else if (path.startsWith(PATH_SHARED)) {
425
            resp.addHeader("Allow", methodsAllowed.get(PATH_SHARED));
426
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
427
                } else if (path.startsWith(PATH_TAGS)) {
428
            resp.addHeader("Allow", methodsAllowed.get(PATH_TAGS));
429
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
430
                } else if (path.startsWith(PATH_GROUPS))
431
                        new GroupsHandler().deleteGroup(req, resp);
432
                else if (path.startsWith(PATH_TRASH))
433
                        new TrashHandler().emptyTrash(req, resp);
434
                else if (path.startsWith(PATH_FILES))
435
                        new FilesHandler(getServletContext()).deleteResource(req, resp);
436
                else
437
                    resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
438
    }
439

    
440
        @Override
441
        protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
442
                boolean authDeferred = getAuthDeferred(req);
443
            // Strip the username part
444
            String path;
445
                try {
446
                        path = getUserPath(req);
447
                } catch (ObjectNotFoundException e) {
448
                        if (authDeferred) {
449
                                // We do not want to leak information if the request
450
                                // was not authenticated.
451
                                resp.sendError(HttpServletResponse.SC_FORBIDDEN);
452
                                return;
453
                        }
454
                        resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
455
                        return;
456
                }
457
                if (authDeferred && !path.startsWith(PATH_FILES)) {
458
                        // Only POST to files may be authenticated without an Authorization header.
459
                        resp.sendError(HttpServletResponse.SC_FORBIDDEN);
460
                        return;
461
                }
462

    
463
                if (path.startsWith(PATH_OTHERS)) {
464
            resp.addHeader("Allow", methodsAllowed.get(PATH_OTHERS));
465
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
466
                } else if (path.startsWith(PATH_SEARCH)) {
467
            resp.addHeader("Allow", methodsAllowed.get(PATH_SEARCH));
468
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
469
                } else if (path.startsWith(PATH_TOKEN)) {
470
            resp.addHeader("Allow", methodsAllowed.get(PATH_TOKEN));
471
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
472
                } else if (path.startsWith(PATH_USERS)) {
473
                        resp.addHeader("Allow", methodsAllowed.get(PATH_USERS));
474
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
475
                } else if (path.startsWith(PATH_SHARED)) {
476
            resp.addHeader("Allow", methodsAllowed.get(PATH_SHARED));
477
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
478
                } else if (path.startsWith(PATH_TAGS)) {
479
            resp.addHeader("Allow", methodsAllowed.get(PATH_TAGS));
480
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
481
                } else if (path.startsWith(PATH_GROUPS))
482
                        new GroupsHandler().postGroup(req, resp);
483
                else if (path.startsWith(PATH_TRASH)) {
484
            resp.addHeader("Allow", methodsAllowed.get(PATH_TRASH));
485
                        resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
486
                } else if (path.startsWith(PATH_FILES))
487
                        new FilesHandler(getServletContext()).postResource(req, resp);
488
                else if (path.equals("/"))
489
                        new UserHandler().postUser(req, resp);
490
                else
491
                    resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
492
        }
493

    
494
        /**
495
         * Return the path inside the user namespace.
496
         *
497
         * @param req the HTTP request
498
         * @return the path after the username part has been removed
499
         * @throws ObjectNotFoundException if the namespace owner was not found
500
         */
501
        private String getUserPath(HttpServletRequest req) throws ObjectNotFoundException {
502
        String path = getRelativePath(req);
503
                if (path.length() < 2)
504
                        return path;
505
                int slash = path.substring(1).indexOf('/');
506
                if (slash == -1)
507
                        return path;
508
                String owner = path.substring(1, slash + 1);
509
                User o;
510
                try {
511
                        o = getService().findUser(owner);
512
                } catch (RpcException e) {
513
                        logger.error("", e);
514
                        throw new ObjectNotFoundException("User " + owner +
515
                                        " not found, due to internal server error");
516
                }
517
                if (o != null) {
518
                        req.setAttribute(OWNER_ATTRIBUTE, o);
519
                        return path.substring(slash + 1);
520
                }
521
                if (!path.startsWith(PATH_SEARCH) && !path.startsWith(PATH_USERS) &&
522
                                !path.startsWith(PATH_TOKEN))
523
                        throw new ObjectNotFoundException("User " + owner + " not found");
524
                return path;
525
        }
526

    
527
        /**
528
         * Retrieve the request context path with or without a trailing slash
529
         * according to the provided argument.
530
         *
531
         * @param req the HTTP request
532
         * @param withTrailingSlash a flag that denotes whether the path should
533
         *                         end with a slash
534
         * @return the context path
535
         */
536
        protected String getContextPath(HttpServletRequest req, boolean withTrailingSlash) {
537
                String contextPath = req.getRequestURL().toString();
538
                if (withTrailingSlash)
539
                        return contextPath.endsWith("/")? contextPath: contextPath + '/';
540
                return contextPath.endsWith("/")? contextPath.substring(0, contextPath.length()-1): contextPath;
541

    
542
        }
543

    
544
        /**
545
         * @param req
546
         * @param resp
547
         * @param json
548
         * @throws UnsupportedEncodingException
549
         * @throws IOException
550
         */
551
        protected void sendJson(HttpServletRequest req, HttpServletResponse resp, String json) throws UnsupportedEncodingException, IOException {
552
                ByteArrayOutputStream stream = new ByteArrayOutputStream();
553
            OutputStreamWriter osWriter = new OutputStreamWriter(stream, "UTF8");
554
            PrintWriter writer = new PrintWriter(osWriter);
555

    
556
            writer.write(json);
557
            writer.flush();
558

    
559
            resp.setContentType("application/json;charset=UTF-8");
560
            resp.setBufferSize(output);
561
                try {
562
                        copy(null, new ByteArrayInputStream(stream.toByteArray()), resp.getOutputStream(), req, null);
563
                } catch (ObjectNotFoundException e) {
564
                        // This should never happen with a null first parameter.
565
                        logger.error("", e);
566
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
567
                        return;
568
                } catch (InsufficientPermissionsException e) {
569
                        // This should never happen with a null first parameter.
570
                        logger.error("", e);
571
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
572
                        return;
573
                } catch (RpcException e) {
574
                        logger.error("", e);
575
                        resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
576
                        return;
577
                }
578
        }
579

    
580
        /**
581
         * Retrieve the path to the requested resource after removing the user namespace
582
         * part and the subsequent namespace part that differentiates resources like files,
583
         * groups, trash, etc.
584
         *
585
         * @param req the HTTP request
586
         * @param namespace the subnamespace
587
         * @return the inner path
588
         */
589
        protected String getInnerPath(HttpServletRequest req, String namespace) {
590
            // Strip the username part
591
            String path;
592
                try {
593
                        path = getUserPath(req);
594
                } catch (ObjectNotFoundException e) {
595
                        throw new RuntimeException(e.getMessage());
596
                }
597
                // Chop the resource namespace part
598
            path = path.substring(namespace.length());
599
                return path;
600
        }
601

    
602
        /**
603
         * Confirms the validity of the request.
604
         *
605
         * @param request the incoming HTTP request
606
         * @return true if the request is valid, false otherwise
607
         */
608
        private boolean isRequestValid(HttpServletRequest request) {
609
                if (logger.isDebugEnabled()) {
610
                        Enumeration headers = request.getHeaderNames();
611
                        while (headers.hasMoreElements()) {
612
                                String h = (String) headers.nextElement();
613
                                        logger.debug(h + ": " +        request.getHeader(h));
614
                        }
615
                }
616
                // Fetch the timestamp used to guard against replay attacks.
617
                long timestamp = 0;
618
                boolean useGssDateHeader = true;
619
                try {
620
                        timestamp = request.getDateHeader(GSS_DATE_HEADER);
621
                        if (timestamp == -1) {
622
                                useGssDateHeader = false;
623
                                timestamp = request.getDateHeader(DATE_HEADER);
624
                        }
625
                } catch (IllegalArgumentException e) {
626
                        return false;
627
                }
628
                if (!isTimeValid(timestamp))
629
                        return false;
630

    
631
                // Fetch the Authorization header and find the user specified in it.
632
                String auth = request.getHeader(AUTHORIZATION_HEADER);
633
                if (auth == null)
634
                        return false;
635
                String[] authParts = auth.split(" ");
636
                if (authParts.length != 2)
637
                        return false;
638
                String username = authParts[0];
639
                String signature = authParts[1];
640
                User user = null;
641
                try {
642
                        user = getService().findUser(username);
643
                } catch (RpcException e) {
644
                        return false;
645
                }
646
                if (user == null)
647
                        return false;
648

    
649
                request.setAttribute(USER_ATTRIBUTE, user);
650

    
651
                // Validate the signature in the Authorization header.
652
                String dateHeader = useGssDateHeader? request.getHeader(GSS_DATE_HEADER):
653
                        request.getHeader(DATE_HEADER);
654
                String data;
655
                // Remove the servlet path from the request URI.
656
                String p = request.getRequestURI();
657
                String servletPath = request.getContextPath() + request.getServletPath();
658
                p = p.substring(servletPath.length());
659
                data = request.getMethod() + dateHeader + p;
660
                return isSignatureValid(signature, user, data);
661
        }
662

    
663
        /**
664
         * Calculates the signature for the specified data String and then
665
         * compares it against the provided signature. If the signatures match,
666
         * the method returns true. Otherwise it returns false.
667
         *
668
         * @param signature the signature to compare against
669
         * @param user the current user
670
         * @param data the data to sign
671
         * @return true if the calculated signature matches the supplied one
672
         */
673
        protected boolean isSignatureValid(String signature, User user, String data) {
674
                if (logger.isDebugEnabled())
675
                        logger.debug("server pre-signing data: "+data);
676
                String serverSignature = null;
677
                // If the authentication token is not valid, the user must get another one.
678
                if (user.getAuthToken() == null)
679
                        return false;
680
                // Get an HMAC-SHA1 key from the authentication token.
681
                SecretKeySpec signingKey = new SecretKeySpec(user.getAuthToken(), HMAC_SHA1);
682
                try {
683
                        // Get an HMAC-SHA1 Mac instance and initialize with the signing key.
684
                        Mac mac = Mac.getInstance(HMAC_SHA1);
685
                        mac.init(signingKey);
686
                        // Compute the HMAC on input data bytes.
687
                        byte[] rawHmac = mac.doFinal(data.getBytes());
688
                        serverSignature = new String(Base64.encodeBase64(rawHmac), "US-ASCII");
689
                } catch (Exception e) {
690
                        logger.error("Error while creating signature", e);
691
                        return false;
692
                }
693

    
694
                if (logger.isDebugEnabled())
695
                        logger.debug("Signature: client="+signature+", server="+serverSignature);
696
                if (!serverSignature.equals(signature))
697
                        return false;
698

    
699
                return true;
700
        }
701

    
702
        /**
703
         * A helper method that checks if the timestamp of the request
704
         * is within TIME_SKEW milliseconds of the current time. If
705
         * the timestamp is older (or even newer) than that, it is
706
         * considered invalid.
707
         *
708
         * @param timestamp the time of the request
709
         * @return true if the timestamp is valid
710
         */
711
        protected boolean isTimeValid(long timestamp) {
712
                if (timestamp == -1)
713
                        return false;
714
                Calendar cal = Calendar.getInstance();
715
                if (logger.isDebugEnabled())
716
                        logger.debug("Time: server=" + cal.getTimeInMillis() + ", client=" + timestamp);
717
                // Ignore the request if the timestamp is too far off.
718
                if (Math.abs(timestamp - cal.getTimeInMillis()) > getConfiguration().getInt("timeSkew", 600000))
719
                        return false;
720
                return true;
721
        }
722

    
723
        protected boolean getAuthDeferred(HttpServletRequest req) {
724
                Boolean attr = (Boolean) req.getAttribute(AUTH_DEFERRED_ATTR);
725
                return attr == null? false: attr;
726
        }
727

    
728
        /**
729
         * Return the actual requested path in the API namespace.
730
         *
731
         * @param request the servlet request we are processing
732
         * @return the relative path
733
         */
734
        @Override
735
        protected String getRelativePath(HttpServletRequest request) {
736
                // Remove the servlet path from the request URI.
737
                String p = request.getRequestURI();
738
                String servletPath = request.getContextPath() + request.getServletPath();
739
                String result = p.substring(servletPath.length());
740
                if (result == null || result.equals(""))
741
                        result = "/";
742
                return result;
743

    
744
        }
745

    
746
        /**
747
         * Reject illegal resource names, like '.' or '..'.
748
         */
749
        protected boolean isValidResourceName(String name) {
750
                if (".".equals(name) ||        "..".equals(name))
751
                        return false;
752
                return true;
753
        }
754
}