View Javadoc

1   package com.atlassian.asap.service.core.impl;
2   
3   
4   import com.atlassian.asap.api.Jwt;
5   import com.atlassian.asap.api.exception.CannotRetrieveKeyException;
6   import com.atlassian.asap.api.exception.InvalidTokenException;
7   import com.atlassian.asap.core.keys.KeyProvider;
8   import com.atlassian.asap.core.parser.JwtParser;
9   import com.atlassian.asap.core.validator.JwtClaimsValidator;
10  import com.atlassian.asap.core.validator.JwtValidator;
11  import com.atlassian.asap.core.validator.JwtValidatorImpl;
12  import com.atlassian.asap.nimbus.parser.NimbusJwtParser;
13  import com.atlassian.asap.service.api.ValidationResult;
14  import com.atlassian.asap.service.core.spi.AsapConfiguration;
15  import com.google.common.annotations.VisibleForTesting;
16  
17  import java.security.PublicKey;
18  import java.util.Optional;
19  import java.util.Set;
20  
21  import static com.atlassian.asap.core.JwtConstants.HTTP_AUTHORIZATION_HEADER_VALUE_PREFIX;
22  import static java.util.Objects.requireNonNull;
23  
24  public class TokenValidatorImpl extends AbstractTokenValidator {
25      private final KeyProvider<PublicKey> publicKeyProvider;
26      private final JwtClaimsValidator jwtClaimsValidator;
27      private final JwtParser jwtParser;
28  
29      public TokenValidatorImpl(AsapConfiguration config, KeyProvider<PublicKey> publicKeyProvider,
30                                JwtClaimsValidator jwtClaimsValidator) {
31          this(config, publicKeyProvider, jwtClaimsValidator, new NimbusJwtParser());
32      }
33  
34      @VisibleForTesting
35      TokenValidatorImpl(AsapConfiguration config, KeyProvider<PublicKey> publicKeyProvider,
36                         JwtClaimsValidator jwtClaimsValidator, JwtParser jwtParser) {
37          super(config);
38          this.publicKeyProvider = requireNonNull(publicKeyProvider, "publicKeyProvider");
39          this.jwtClaimsValidator = requireNonNull(jwtClaimsValidator, "jwtClaimsValidator");
40          this.jwtParser = requireNonNull(jwtParser, "jwtParser");
41      }
42  
43      @Override
44      public ValidationResult validate(Optional<String> authHeader) {
45          requireNonNull(authHeader, "authHeader");
46          switch (policy()) {
47              case REJECT:
48                  return rejectAsap(authHeader);
49              case IGNORE:
50                  return ValidationResultImpl.abstain();
51              case OPTIONAL:
52                  return optionalAsap(authHeader);
53              case REQUIRE:
54                  return requireAsap(authHeader);
55              default:
56                  throw new IllegalStateException("Unknown authorization policy: " + policy());
57          }
58      }
59  
60      private ValidationResult rejectAsap(Optional<String> authHeader) {
61          return extractSerializedJwt(authHeader)
62                  .flatMap(jwtParser::determineUnverifiedIssuer)
63                  .map(ValidationResultImpl::rejected)
64                  .orElseGet(ValidationResultImpl::abstain);
65      }
66  
67      private ValidationResult optionalAsap(Optional<String> authHeader) {
68          return extractSerializedJwt(authHeader)
69                  .flatMap(this::parseAndVerifyToken)
70                  .orElseGet(ValidationResultImpl::abstain);
71      }
72  
73      private ValidationResult requireAsap(Optional<String> authHeader) {
74          return extractSerializedJwt(authHeader)
75                  .flatMap(this::parseAndVerifyToken)
76                  .orElseGet(ValidationResultImpl::notAuthenticated);
77      }
78  
79      private Optional<ValidationResult> parseAndVerifyToken(String serializedJwt) {
80          try {
81              final JwtValidator jwtValidator = createJwtValidator(publicKeyProvider, jwtParser, jwtClaimsValidator,
82                      acceptableAudienceValues());
83              final Jwt jwt = jwtValidator.readAndValidate(serializedJwt);
84              return Optional.of(verifyAuthorization(jwt));
85          } catch (InvalidTokenException e) {
86              // This exception doesn't tell us if the token was structurally invalid or if it made claims that
87              // we cannot accept.  If we can pull an issuer out of it, then some kind of unacceptable JWT was
88              // provided, so we should actively reject the request.  Otherwise, we can fall back on the default
89              // handling for the request.
90              return jwtParser.determineUnverifiedIssuer(serializedJwt)
91                      .map(issuer -> ValidationResultImpl.notAuthenticated());
92          } catch (CannotRetrieveKeyException e) {
93              final String issuer = jwtParser.determineUnverifiedIssuer(serializedJwt).orElse(null);
94              return Optional.of(ValidationResultImpl.notVerified(issuer));
95          }
96      }
97  
98      /**
99       * Creates the validator that will enforce correctness of the token.
100      *
101      * @param publicKeyProvider  the source for resolving public keys
102      * @param jwtParser          a parser for deserializing the JWT
103      * @param jwtClaimsValidator the validator for enforcing authorization claims
104      * @param allowedAudiences   the audience values that the token is allowed to specify for authenticating with us
105      * @return a validator for the JWT
106      */
107     protected JwtValidator createJwtValidator(KeyProvider<PublicKey> publicKeyProvider, JwtParser jwtParser,
108                                               JwtClaimsValidator jwtClaimsValidator, Set<String> allowedAudiences) {
109         return new JwtValidatorImpl(publicKeyProvider, jwtParser, jwtClaimsValidator, allowedAudiences);
110     }
111 
112     private ValidationResult verifyAuthorization(Jwt jwt) {
113         if ((isImpersonationIssuerAuthorized(jwt) || isIssuerAuthorized(jwt)) && isSubjectAuthorized(jwt)) {
114             return ValidationResultImpl.authorized(jwt);
115         }
116 
117         return ValidationResultImpl.notAuthorized(jwt.getClaims().getIssuer());
118     }
119 
120     private boolean subjectImpersonation(Jwt jwt) {
121         return subjectImpersonation() || isImpersonationIssuerAuthorized(jwt);
122     }
123 
124     private boolean isImpersonationIssuerAuthorized(Jwt jwt) {
125         return impersonationAuthorizedIssuers().contains(jwt.getClaims().getIssuer());
126     }
127 
128     //simplify as soon as deprecated com.atlassian.asap.service.api.AsapAuth.subjectImpersonation is removed
129     private boolean isIssuerAuthorized(Jwt jwt) {
130         if (authorizedIssuers().isEmpty()) {
131             if (subjectImpersonation(jwt)) {
132                 throw new IllegalStateException("Subject impersonation requires an explicit issuer whitelist");
133             }
134             return true;
135         }
136         return authorizedIssuers().contains(jwt.getClaims().getIssuer());
137     }
138 
139     private boolean isSubjectAuthorized(Jwt jwt) {
140         final Set<String> subjects = authorizedSubjects();
141         if (subjects.isEmpty()) {
142             return true;
143         }
144 
145         // The effective subject is the issuer if and only if the subject was unspecified and subject impersonation is not used.
146         final String defaultSubject = subjectImpersonation(jwt) ? null : jwt.getClaims().getIssuer();
147         final String effectiveSubject = jwt.getClaims().getSubject().orElse(defaultSubject);
148         return subjects.contains(effectiveSubject);
149     }
150 
151     private static Optional<String> extractSerializedJwt(Optional<String> authHeader) {
152         return authHeader
153                 .filter(hdr -> hdr.startsWith(HTTP_AUTHORIZATION_HEADER_VALUE_PREFIX))
154                 .map(hdr -> hdr.substring(HTTP_AUTHORIZATION_HEADER_VALUE_PREFIX.length()));
155     }
156 }