2 * Copyright 2008, 2009 Electronic Business Systems Ltd.
4 * This file is part of GSS.
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.
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.
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/>.
19 package gr.ebs.gss.server.rest;
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;
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;
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;
46 import org.apache.commons.codec.binary.Base64;
47 import org.apache.commons.logging.Log;
48 import org.apache.commons.logging.LogFactory;
51 * The servlet that handles requests for the REST API.
55 public class RequestHandler extends Webdav {
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.
61 protected static final String AUTH_DEFERRED_ATTR = "authDeferred";
64 * The path for the search subsystem.
66 protected static final String PATH_SEARCH = "/search";
69 * The path for the user search subsystem.
71 protected static final String PATH_USERS = "/users";
74 * The path for the resource manipulation subsystem.
76 protected static final String PATH_FILES = FileHeaderDTO.PATH_FILES;
79 * The path for the trash virtual folder.
81 protected static final String PATH_TRASH = "/trash";
84 * The path for the subsystem that deals with the user attributes.
86 protected static final String PATH_GROUPS = "/groups";
89 * The path for the shared resources virtual folder.
91 protected static final String PATH_SHARED = "/shared";
94 * The path for the other users' shared resources virtual folder.
96 protected static final String PATH_OTHERS = "/others";
99 * The path for tags created by the user.
101 protected static final String PATH_TAGS = "/tags";
104 * The path for token renewal.
106 protected static final String PATH_TOKEN = "/newtoken";
109 * The GSS-specific header for the request timestamp.
111 private static final String GSS_DATE_HEADER = "X-GSS-Date";
114 * The RFC 2616 date header.
116 private static final String DATE_HEADER = "Date";
119 * The Authorization HTTP header.
121 private static final String AUTHORIZATION_HEADER = "Authorization";
124 * The group parameter name.
126 protected static final String GROUP_PARAMETER = "name";
129 * The username parameter name.
131 protected static final String USERNAME_PARAMETER = "name";
134 * The "new folder name" parameter name.
136 protected static final String NEW_FOLDER_PARAMETER = "new";
139 * The resource update parameter name.
141 protected static final String RESOURCE_UPDATE_PARAMETER = "update";
144 * The resource trash parameter name.
146 protected static final String RESOURCE_TRASH_PARAMETER = "trash";
149 * The resource restore parameter name.
151 protected static final String RESOURCE_RESTORE_PARAMETER = "restore";
154 * The resource copy parameter name.
156 protected static final String RESOURCE_COPY_PARAMETER = "copy";
159 * The resource move parameter name.
161 protected static final String RESOURCE_MOVE_PARAMETER = "move";
164 * The HMAC-SHA1 hash name.
166 private static final String HMAC_SHA1 = "HmacSHA1";
169 * The serial version UID of the class.
171 private static final long serialVersionUID = 1L;
176 private static Log logger = LogFactory.getLog(RequestHandler.class);
179 * Create a mapping between paths and allowed HTTP methods for fast lookup.
181 private final Map<String, String> methodsAllowed = new HashMap<String, String>(7);
184 public void init() throws ServletException {
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);
200 * Return the root of every API request URL.
202 protected String getApiRoot() {
203 return getConfiguration().getString("restUrl", "http://localhost:8080/gss/rest/");
207 public void service(final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException {
208 String method = request.getMethod();
209 String path = getRelativePath(request);
211 if (logger.isDebugEnabled())
212 logger.debug("[" + method + "] " + path);
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);
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);
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);
238 response.sendError(HttpServletResponse.SC_BAD_REQUEST);
242 protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
243 boolean authDeferred = getAuthDeferred(req);
244 // Strip the username part
247 path = getUserPath(req);
248 } catch (ObjectNotFoundException e) {
250 // We do not want to leak information if the request
251 // was not authenticated.
252 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
255 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
258 if (authDeferred && !path.startsWith(PATH_FILES)) {
259 // Only files may be open to the public.
260 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
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);
292 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
296 * Handle storing and updating file resources.
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
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
308 path = getUserPath(req);
309 } catch (ObjectNotFoundException e) {
310 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
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);
341 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
345 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
346 boolean authDeferred = getAuthDeferred(req);
347 // Strip the username part
350 path = getUserPath(req);
351 } catch (ObjectNotFoundException e) {
353 // We do not want to leak information if the request
354 // was not authenticated.
355 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
358 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
361 if (authDeferred && !path.startsWith(PATH_FILES)) {
362 // Only files may be open to the public.
363 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
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);
390 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
394 * Handle a Delete request.
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
401 protected void doDelete(HttpServletRequest req, HttpServletResponse resp)
403 // Strip the username part
406 path = getUserPath(req);
407 } catch (ObjectNotFoundException e) {
408 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
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);
437 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
441 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
442 boolean authDeferred = getAuthDeferred(req);
443 // Strip the username part
446 path = getUserPath(req);
447 } catch (ObjectNotFoundException e) {
449 // We do not want to leak information if the request
450 // was not authenticated.
451 resp.sendError(HttpServletResponse.SC_FORBIDDEN);
454 resp.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
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);
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);
491 resp.sendError(HttpServletResponse.SC_NOT_FOUND, req.getRequestURI());
495 * Return the path inside the user namespace.
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
501 private String getUserPath(HttpServletRequest req) throws ObjectNotFoundException {
502 String path = getRelativePath(req);
503 if (path.length() < 2)
505 int slash = path.substring(1).indexOf('/');
508 String owner = path.substring(1, slash + 1);
511 o = getService().findUser(owner);
512 } catch (RpcException e) {
514 throw new ObjectNotFoundException("User " + owner +
515 " not found, due to internal server error");
518 req.setAttribute(OWNER_ATTRIBUTE, o);
519 return path.substring(slash + 1);
521 if (!path.startsWith(PATH_SEARCH) && !path.startsWith(PATH_USERS) &&
522 !path.startsWith(PATH_TOKEN))
523 throw new ObjectNotFoundException("User " + owner + " not found");
528 * Retrieve the request context path with or without a trailing slash
529 * according to the provided argument.
531 * @param req the HTTP request
532 * @param withTrailingSlash a flag that denotes whether the path should
534 * @return the context path
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;
548 * @throws UnsupportedEncodingException
549 * @throws IOException
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);
559 resp.setContentType("application/json;charset=UTF-8");
560 resp.setBufferSize(output);
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.
566 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
568 } catch (InsufficientPermissionsException e) {
569 // This should never happen with a null first parameter.
571 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
573 } catch (RpcException e) {
575 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
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.
585 * @param req the HTTP request
586 * @param namespace the subnamespace
587 * @return the inner path
589 protected String getInnerPath(HttpServletRequest req, String namespace) {
590 // Strip the username part
593 path = getUserPath(req);
594 } catch (ObjectNotFoundException e) {
595 throw new RuntimeException(e.getMessage());
597 // Chop the resource namespace part
598 path = path.substring(namespace.length());
603 * Confirms the validity of the request.
605 * @param request the incoming HTTP request
606 * @return true if the request is valid, false otherwise
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));
616 // Fetch the timestamp used to guard against replay attacks.
618 boolean useGssDateHeader = true;
620 timestamp = request.getDateHeader(GSS_DATE_HEADER);
621 if (timestamp == -1) {
622 useGssDateHeader = false;
623 timestamp = request.getDateHeader(DATE_HEADER);
625 } catch (IllegalArgumentException e) {
628 if (!isTimeValid(timestamp))
631 // Fetch the Authorization header and find the user specified in it.
632 String auth = request.getHeader(AUTHORIZATION_HEADER);
635 String[] authParts = auth.split(" ");
636 if (authParts.length != 2)
638 String username = authParts[0];
639 String signature = authParts[1];
642 user = getService().findUser(username);
643 } catch (RpcException e) {
649 request.setAttribute(USER_ATTRIBUTE, user);
651 // Validate the signature in the Authorization header.
652 String dateHeader = useGssDateHeader? request.getHeader(GSS_DATE_HEADER):
653 request.getHeader(DATE_HEADER);
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);
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.
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
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)
680 // Get an HMAC-SHA1 key from the authentication token.
681 SecretKeySpec signingKey = new SecretKeySpec(user.getAuthToken(), HMAC_SHA1);
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);
694 if (logger.isDebugEnabled())
695 logger.debug("Signature: client="+signature+", server="+serverSignature);
696 if (!serverSignature.equals(signature))
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.
708 * @param timestamp the time of the request
709 * @return true if the timestamp is valid
711 protected boolean isTimeValid(long timestamp) {
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))
723 protected boolean getAuthDeferred(HttpServletRequest req) {
724 Boolean attr = (Boolean) req.getAttribute(AUTH_DEFERRED_ATTR);
725 return attr == null? false: attr;
729 * Return the actual requested path in the API namespace.
731 * @param request the servlet request we are processing
732 * @return the relative path
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(""))