Statistics
| Branch: | Tag: | Revision:

root / src / org / gss_project / gss / server / rest / RequestHandler.java @ 1206:292dec4eae08

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 org.gss_project.gss.server.rest;
20

    
21
import static org.gss_project.gss.server.configuration.GSSConfigurationFactory.getConfiguration;
22
import org.gss_project.gss.common.exceptions.InsufficientPermissionsException;
23
import org.gss_project.gss.common.exceptions.ObjectNotFoundException;
24
import org.gss_project.gss.common.exceptions.RpcException;
25
import org.gss_project.gss.server.domain.FileHeader;
26
import org.gss_project.gss.server.domain.User;
27

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

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

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

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

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

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

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

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

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

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

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

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

    
102
        /**
103
         * The path for token renewal.
104
         */
105
        protected static final String PATH_TOKEN = "/newtoken";
106

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
541
        }
542

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

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

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

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

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

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

    
648
                request.setAttribute(USER_ATTRIBUTE, user);
649

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

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

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

    
698
                return true;
699
        }
700

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

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

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

    
743
        }
744

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