View Javadoc

1   package com.atlassian.asap.core.server.jersey;
2   
3   import com.atlassian.asap.api.Jwt;
4   import com.atlassian.asap.api.exception.AuthenticationFailedException;
5   import com.atlassian.asap.api.exception.PermanentAuthenticationFailedException;
6   import com.atlassian.asap.api.exception.TransientAuthenticationFailedException;
7   import com.atlassian.asap.api.server.http.RequestAuthenticator;
8   import com.atlassian.asap.core.keys.KeyProvider;
9   import com.atlassian.asap.core.server.AuthenticationContext;
10  import com.atlassian.asap.core.server.http.RequestAuthenticatorFactory;
11  import org.slf4j.Logger;
12  import org.slf4j.LoggerFactory;
13  
14  import javax.annotation.Nullable;
15  import javax.annotation.Priority;
16  import javax.ws.rs.Priorities;
17  import javax.ws.rs.container.ContainerRequestContext;
18  import javax.ws.rs.container.ContainerRequestFilter;
19  import javax.ws.rs.container.ResourceInfo;
20  import javax.ws.rs.core.Context;
21  import javax.ws.rs.core.HttpHeaders;
22  import javax.ws.rs.ext.Provider;
23  import java.io.IOException;
24  import java.security.PublicKey;
25  import java.util.Optional;
26  
27  import static com.atlassian.asap.core.server.filter.AbstractRequestAuthenticationFilter.AUTHENTIC_JWT_REQUEST_ATTRIBUTE;
28  import static java.util.Objects.requireNonNull;
29  
30  /**
31   * AuthenticationRequestFilter is a {@link ContainerRequestFilter} that authenticates resources with the ASAP protocol
32   * if opted-into by using the {@link Asap} annotation on either a resource package, class, or method.
33   */
34  @Provider
35  @Priority(Priorities.AUTHENTICATION)
36  public class AuthenticationRequestFilter implements ContainerRequestFilter {
37      static final int MAX_TRANSIENT_FAILURES_RETRIES = 10;
38  
39      static final String ASAP_REQUEST_ATTRIBUTE = "asap.annotation";
40  
41      private static final Logger LOG = LoggerFactory.getLogger(AuthenticationRequestFilter.class);
42  
43      @SuppressWarnings("checkstyle:VisibilityModifier")
44      @Context
45      ResourceInfo resourceInfo;
46  
47      private final RequestAuthenticator authenticator;
48      private final FailureHandler failureHandler;
49  
50      @SuppressWarnings("WeakerAccess")
51      public AuthenticationRequestFilter(RequestAuthenticator authenticator, FailureHandler failureHandler) {
52          this.failureHandler = requireNonNull(failureHandler);
53          this.authenticator = requireNonNull(authenticator);
54      }
55  
56      @Override
57      public void filter(ContainerRequestContext context) throws IOException {
58          needsAuthentication().ifPresent(asap ->
59                  {
60                      context.setProperty(AUTHENTIC_JWT_REQUEST_ATTRIBUTE, authenticateToken(context));
61                      context.setProperty(ASAP_REQUEST_ATTRIBUTE, asap);
62                  }
63          );
64      }
65  
66      @Nullable
67      private Jwt authenticateToken(final ContainerRequestContext context) {
68          final String authorizationHeader = context.getHeaderString(HttpHeaders.AUTHORIZATION);
69  
70          for (int tryCounter = 0; tryCounter < MAX_TRANSIENT_FAILURES_RETRIES; tryCounter++) {
71              try {
72                  Jwt authenticJwt = authenticator.authenticateRequest(authorizationHeader);
73                  LOG.trace("Accepting authentic token with identifier '{}'", authenticJwt.getClaims().getJwtId());
74                  return authenticJwt;
75              } catch (TransientAuthenticationFailedException e) {
76                  if (tryCounter >= MAX_TRANSIENT_FAILURES_RETRIES - 1) { // we've done the max number of retries
77                      failureHandler.onAuthenticationFailure(context, e);
78                      return null;
79                  } else {
80                      final boolean retry = failureHandler.onTransientAuthenticationFailure(context, e);
81                      if (!retry) {
82                          return null; // request should have already been aborted by the failure handler
83                      }
84                  }
85              } catch (PermanentAuthenticationFailedException e) {
86                  failureHandler.onPermanentAuthenticationFailure(context, e);
87                  return null;
88              } catch (AuthenticationFailedException e) {
89                  failureHandler.onAuthenticationFailure(context, e);
90                  return null;
91              }
92          }
93          throw new IllegalStateException();
94      }
95  
96      /**
97       * Finds the {@link Asap} token on the resource package, class, or method.
98       */
99      private Optional<Asap> needsAuthentication() {
100         Asap asap = null;
101         if (resourceInfo.getResourceMethod().isAnnotationPresent(Asap.class)) {
102             asap = resourceInfo.getResourceMethod().getAnnotation(Asap.class);
103         } else if (resourceInfo.getResourceClass().isAnnotationPresent(Asap.class)) {
104             asap = resourceInfo.getResourceClass().getAnnotation(Asap.class);
105         } else if (resourceInfo.getResourceClass().getPackage().isAnnotationPresent(Asap.class)) {
106             asap = resourceInfo.getResourceClass().getPackage().getAnnotation(Asap.class);
107         }
108         return Optional.ofNullable(asap)
109                 .filter(Asap::enabled);
110     }
111 
112 
113     /**
114      * Use this factory method to create a new AuthorizationRequestFilter instance with the specified audience and
115      * public key repository URL.
116      *
117      * @param audience the non-null audience for this filter
118      * @param repoUrl  the ASAP public key repository URL
119      * @return the new instance
120      */
121     public static AuthenticationRequestFilter newInstance(String audience, String repoUrl) {
122         return newInstance(new AuthenticationContext(audience, repoUrl));
123     }
124 
125     /**
126      * Use this factory method to create a new AuthorizationRequestFilter instance with the specified audience and
127      * public key repository provider.
128      *
129      * @param audience    the non-null audience for this filter
130      * @param keyProvider the ASAP public key provider
131      * @return the new instance
132      */
133     public static AuthenticationRequestFilter newInstance(String audience, KeyProvider<PublicKey> keyProvider) {
134         return newInstance(new AuthenticationContext(audience, keyProvider));
135     }
136 
137     /**
138      * Use this factory method to create a new AuthorizationRequestFilter instance with the specified context.
139      *
140      * @param authenticationContext the non-null authentication context
141      * @return the new instance
142      */
143     public static AuthenticationRequestFilter newInstance(AuthenticationContext authenticationContext) {
144         RequestAuthenticatorFactory requestAuthenticatorFactory = new RequestAuthenticatorFactory();
145         RequestAuthenticator authenticator = requestAuthenticatorFactory.create(authenticationContext);
146 
147         return new AuthenticationRequestFilter(authenticator, new EmptyBodyFailureHandler());
148     }
149 }