View Javadoc

1   package com.atlassian.asap.core.server.http;
2   
3   import com.atlassian.asap.api.Jwt;
4   import com.atlassian.asap.api.exception.AuthenticationFailedException;
5   import com.atlassian.asap.api.exception.CannotRetrieveKeyException;
6   import com.atlassian.asap.api.exception.InvalidTokenException;
7   import com.atlassian.asap.api.exception.PermanentAuthenticationFailedException;
8   import com.atlassian.asap.api.exception.TransientAuthenticationFailedException;
9   import com.atlassian.asap.api.server.http.RequestAuthenticator;
10  import com.atlassian.asap.core.JwtConstants;
11  import com.atlassian.asap.core.exception.PublicKeyNotFoundException;
12  import com.atlassian.asap.core.validator.JwtValidator;
13  import org.apache.commons.lang3.StringUtils;
14  import org.slf4j.Logger;
15  import org.slf4j.LoggerFactory;
16  
17  import java.util.Objects;
18  import java.util.Optional;
19  
20  import static java.lang.String.format;
21  
22  public class RequestAuthenticatorImpl implements RequestAuthenticator {
23      private static final Logger logger = LoggerFactory.getLogger(RequestAuthenticatorImpl.class);
24  
25      private final JwtValidator jwtValidator;
26  
27      public RequestAuthenticatorImpl(JwtValidator jwtValidator) {
28          this.jwtValidator = Objects.requireNonNull(jwtValidator);
29      }
30  
31      @Override
32      public Jwt authenticateRequest(String authorizationHeader) throws AuthenticationFailedException {
33          if (StringUtils.isBlank(authorizationHeader)) {
34              throw new PermanentAuthenticationFailedException("Authorization header is missing");
35          }
36  
37          if (!authorizationHeader.startsWith(JwtConstants.HTTP_AUTHORIZATION_HEADER_VALUE_PREFIX)) {
38              throw new PermanentAuthenticationFailedException("Authorization header is not in the expected format. Expected format is 'Bearer <jwt token>'");
39          }
40  
41          String serializedJwt = StringUtils.removeStart(authorizationHeader, JwtConstants.HTTP_AUTHORIZATION_HEADER_VALUE_PREFIX);
42          try {
43              return jwtValidator.readAndValidate(serializedJwt);
44          } catch (PublicKeyNotFoundException e) {
45              // log at debug because this can be caused by invalid input
46              logger.debug("Public key not found when authenticating request: {}", e.getKeyId(), e);
47              // we generally deliberately choose NOT to chain exceptions or their messages (to avoid leaking sensitive
48              // private details into the caller's logs).  However PublicKeyNotFoundException messages should relate to
49              // already publicly available information, so preserving the message here should be safe.
50              final Optional<String> issuer = getUnverifiedIssuer(serializedJwt);
51              throw new PermanentAuthenticationFailedException(
52                      format("Public key not found when authenticating request from %s: %s",
53                              formatIssuer(issuer), e.getMessage()),
54                      issuer.orElse(null));
55          } catch (CannotRetrieveKeyException e) { // a failure when retrieving the key
56              // log at error because this indicates a system failure
57              logger.error("Error retrieving key required to authenticate request", e);
58              // we generally deliberately choose NOT to chain exceptions or their messages (to avoid leaking sensitive
59              // private details into the caller's logs).  However CannotRetrieveKeyException messages should relate to
60              // already publicly available information, so preserving the message here should be safe.
61              final Optional<String> issuer = getUnverifiedIssuer(serializedJwt);
62              throw new TransientAuthenticationFailedException(
63                      format("Failed to retrieve the key required to authenticate request from %s: %s",
64                              formatIssuer(issuer), e.getMessage()),
65                      e.getKeyId().orElse(null),
66                      e.getKeyUri().orElse(null),
67                      issuer.orElse(null));
68          } catch (InvalidTokenException e) {
69              // log at debug because this can be caused by invalid input
70              logger.debug("Failed to authenticate request", e);
71              // we deliberately choose NOT to chain exceptions or their messages (to avoid leaking sensitive private
72              // details into the caller's logs).  However, that leaves the caller totally unawares as to what went wrong.
73              // A compromise here is to propagate some safe details from the exception, being the class name, and,
74              // possibly, depending on the type of exception, some further information about the failure.
75              final Optional<String> issuer = getUnverifiedIssuer(serializedJwt);
76              throw new PermanentAuthenticationFailedException(
77                      format("Failed to authenticate request from %s: %s", formatIssuer(issuer), e.getSafeDetails()),
78                      issuer.orElse(null));
79          }
80      }
81  
82      private Optional<String> getUnverifiedIssuer(String serializedJwt) {
83          return jwtValidator.determineUnverifiedIssuer(serializedJwt);
84      }
85  
86      @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
87      private static String formatIssuer(Optional<String> issuer) {
88          return issuer.map(iss -> iss + " (issuer not verified)").orElse("unknown issuer");
89      }
90  }