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.AuthorizationFailedException;
5   import org.apache.commons.lang3.StringUtils;
6   
7   import java.util.Arrays;
8   import java.util.Collections;
9   import java.util.Map;
10  import java.util.Set;
11  import java.util.function.Function;
12  import java.util.stream.Collectors;
13  import java.util.stream.Stream;
14  
15  import static com.atlassian.asap.core.server.jersey.Memoizer.memoize;
16  import static java.lang.String.format;
17  import static java.util.Objects.requireNonNull;
18  
19  /**
20   * Validates a valid {@link Jwt} token against a whitelist of acceptable subjects and/or issuers.
21   */
22  @SuppressWarnings("WeakerAccess")
23  public class AsapValidator {
24  
25      private final Function<Asap, Whitelist> whitelistProvider;
26  
27      public AsapValidator(Function<Asap, Whitelist> whitelistProvider) {
28          this.whitelistProvider = requireNonNull(whitelistProvider);
29      }
30  
31  
32      public static AsapValidator newAnnotationValidator() {
33          return new AsapValidator(memoize(new AsapWhitelistProvider()));
34      }
35  
36      public static AsapValidator newEnvironmentVariablesValidator() {
37          return new AsapValidator(memoize(new EnvironmentVariablesWhitelistProvider()));
38      }
39  
40      /**
41       * This validator gives preferential treatment to the subjects and issuers present in the Asap annotation.
42       * If either of them is not present in the asap annotation then it falls back to the supplied one.
43       *
44       * @param authorizedSubjects - Set of authorized subjects
45       * @param authorizedIssuers  - Set of authorized Issuers
46       * @return AsapValidator with annotation support with fallback to configuration
47       */
48      public static AsapValidator newAnnotationWithConfigValidator(final Set<String> authorizedSubjects, final Set<String> authorizedIssuers) {
49          return new AsapValidator(new AsapAnnotationWhitelistProviderWithConfigSupport(authorizedSubjects, authorizedIssuers));
50      }
51  
52  
53      private static String joinSet(final Set<String> validIssuers) {
54          return String.join(",", validIssuers);
55      }
56  
57      /**
58       * Validates a jwt token.
59       *
60       * @param asap The annotation instance on a method, class, or package
61       * @param jwt  The jwt token
62       * @throws AuthorizationFailedException If the authorized subjects or issuers fails
63       */
64      public void validate(Asap asap, Jwt jwt) throws AuthorizationFailedException {
65          Whitelist whitelist = whitelistProvider.apply(asap);
66          validateSubject(whitelist, jwt);
67          validateIssuer(whitelist, jwt);
68      }
69  
70      private void validateIssuer(final Whitelist whitelist, final Jwt jwt)
71              throws AuthorizationFailedException {
72          String issuer = jwt.getClaims().getIssuer();
73          Set<String> validIssuers = !whitelist.authorizedIssuers.isEmpty() ? whitelist.authorizedIssuers :
74                  whitelist.authorizedSubjects;
75          if (!validIssuers.isEmpty() && !validIssuers.contains(issuer)) {
76              throw new AuthorizationFailedException(format("Unacceptable issuer ('%s' not in '%s')", issuer,
77                      joinSet(validIssuers)));
78          }
79      }
80  
81      private void validateSubject(final Whitelist whitelist, final Jwt jwt) throws AuthorizationFailedException {
82          String subject = jwt.getClaims().getSubject().orElse(jwt.getClaims().getIssuer());
83          if (!whitelist.authorizedSubjects.isEmpty() &&
84                  !whitelist.authorizedSubjects.contains(subject)) {
85              throw new AuthorizationFailedException(format("Unacceptable subject ('%s' not in '%s')", subject,
86                      joinSet(whitelist.authorizedSubjects)));
87          }
88      }
89  
90      /**
91       * Provides whitelisted values.  If empty, treat as all allowed.
92       */
93      @SuppressWarnings("checkstyle:VisiblityModifier")
94      public static class Whitelist {
95          private final Set<String> authorizedSubjects;
96          private final Set<String> authorizedIssuers;
97  
98          public Whitelist(final Set<String> authorizedSubjects, final Set<String> authorizedIssuers) {
99              this.authorizedSubjects = Collections.unmodifiableSet(requireNonNull(authorizedSubjects));
100             this.authorizedIssuers = Collections.unmodifiableSet(requireNonNull(authorizedIssuers));
101         }
102 
103         Set<String> getAuthorizedSubjects() {
104             return authorizedSubjects;
105         }
106 
107         Set<String> getAuthorizedIssuers() {
108             return authorizedIssuers;
109         }
110     }
111 
112     public static class AsapWhitelistProvider implements Function<Asap, Whitelist> {
113         @Override
114         public Whitelist apply(final Asap asap) {
115             return new Whitelist(
116                     Arrays.stream(asap.authorizedSubjects()).collect(Collectors.toSet()),
117                     Arrays.stream(asap.authorizedIssuers()).collect(Collectors.toSet())
118             );
119         }
120     }
121 
122 
123     public static class AsapAnnotationWhitelistProviderWithConfigSupport implements Function<Asap, Whitelist> {
124 
125         private final Whitelist whiteList;
126 
127         public AsapAnnotationWhitelistProviderWithConfigSupport(final Set<String> authorizedSubjects, final Set<String> authorizedIssuers) {
128             this.whiteList = new Whitelist(authorizedSubjects, authorizedIssuers);
129 
130         }
131 
132         @Override
133         public Whitelist apply(final Asap asap) {
134 
135             if (asap.authorizedIssuers().length == 0 && asap.authorizedSubjects().length == 0) {
136                 return whiteList;
137             }
138             return new Whitelist(
139                     Arrays.stream(asap.authorizedSubjects()).collect(Collectors.toSet()),
140                     Arrays.stream(asap.authorizedIssuers()).collect(Collectors.toSet())
141             );
142         }
143     }
144 
145 
146     /**
147      * Provides the whitelist from environment variables.  The values should be comma-delimited, and an empty or missing
148      * value will be treated as allow all.
149      */
150     public static class EnvironmentVariablesWhitelistProvider implements Function<Asap, Whitelist> {
151         public static final String AUTHORIZED_SUBJECTS_KEY = "ASAP_AUTHORIZED_SUBJECTS";
152         public static final String AUTHORIZED_ISSUERS_KEY = "ASAP_AUTHORIZED_ISSUERS";
153 
154         private final String authorizedSubjectsVariableName;
155         private final String authorizedIssuersVariableName;
156         private final Map<String, String> variables;
157 
158         public EnvironmentVariablesWhitelistProvider() {
159             this(AUTHORIZED_SUBJECTS_KEY, AUTHORIZED_ISSUERS_KEY, System.getenv());
160         }
161 
162         public EnvironmentVariablesWhitelistProvider(final String authorizedSubjectsVariableName,
163                                                      final String authorizedIssuersVariableName, Map<String, String> variables) {
164             this.authorizedSubjectsVariableName = requireNonNull(authorizedSubjectsVariableName);
165             this.authorizedIssuersVariableName = requireNonNull(authorizedIssuersVariableName);
166             this.variables = variables;
167         }
168 
169         @Override
170         public Whitelist apply(final Asap asap) {
171             return new Whitelist(
172                     getEnv(authorizedSubjectsVariableName),
173                     getEnv(authorizedIssuersVariableName)
174             );
175         }
176 
177         private Set<String> getEnv(String name) {
178             return Stream.of(variables.getOrDefault(name, "").split(","))
179                     .map(String::trim)
180                     .filter(StringUtils::isNotEmpty)
181                     .collect(Collectors.toSet());
182         }
183     }
184 }