View Javadoc

1   package com.atlassian.asap.core.keys.publickey;
2   
3   import com.atlassian.asap.core.keys.KeyProvider;
4   import com.atlassian.asap.core.keys.PemReader;
5   import org.apache.commons.lang3.StringUtils;
6   import org.apache.http.client.HttpClient;
7   import org.slf4j.Logger;
8   import org.slf4j.LoggerFactory;
9   
10  import java.io.File;
11  import java.net.URI;
12  import java.security.PublicKey;
13  import java.util.List;
14  import java.util.Objects;
15  import java.util.regex.Pattern;
16  import java.util.stream.Collectors;
17  
18  import static com.google.common.base.Preconditions.checkArgument;
19  
20  /**
21   * <p>Factory for creating {@link KeyProvider KeyProvider&lt;PublicKey&gt;} instances.</p>
22   *
23   * <p>The factory supports multiple key repositories for fail overs. The key repositories can be specified as mirrored or chained
24   * in the public key base url.
25   * <ul>
26   *     <li>Chained key repositories must be comma (,) separated, with one or more whitespace before and after the comma.
27   *         They are accessed in the order they are specified.</li>
28   *     <li>Mirrored key repositories must be separated by the pipe (|) character, with one or more whitespace before and after the pipe.</li>
29   *  </ul>
30   * <p> The repository types can be combined, e.g. for a public key base url <code>"a , b | c , d"</code>,
31   * the public key is looked-up first in a, if it fails then b or c, and then finally c. </p>
32   *
33   * @see ChainedKeyProvider
34   * @see MirroredKeyProvider
35   */
36  public class PublicKeyProviderFactory {
37      private static final Pattern CHAIN_SPLITTER_REGEX = Pattern.compile("\\s+,\\s+");
38      private static final Pattern MIRRORS_SPLITTER_REGEX = Pattern.compile("\\s+\\|\\s+");
39  
40      private static final Logger logger = LoggerFactory.getLogger(PublicKeyProviderFactory.class);
41  
42      private final PemReader pemReader;
43      private final HttpClient httpClient;
44  
45      public PublicKeyProviderFactory(HttpClient httpClient, PemReader pemReader) {
46          this.pemReader = Objects.requireNonNull(pemReader);
47          this.httpClient = Objects.requireNonNull(httpClient);
48      }
49  
50      /**
51       * @return a default instance of the factory
52       */
53      public static PublicKeyProviderFactory createDefault() {
54          return new PublicKeyProviderFactory(HttpPublicKeyProvider.defaultHttpClient(), new PemReader());
55      }
56  
57      /**
58       * Creates an instance of {@link com.atlassian.asap.core.keys.KeyProvider} for the given base URL.
59       *
60       * @param publicKeyBaseUrl the base URL of the public key server. See the class Javadoc for syntax details.
61       * @return a public key provider
62       * @throws IllegalArgumentException if the publicKeyBaseUrl is syntactically incorrect
63       */
64      public KeyProvider<PublicKey> createPublicKeyProvider(String publicKeyBaseUrl) {
65          Objects.requireNonNull(publicKeyBaseUrl);
66          logger.info("Using {} as public key base url", publicKeyBaseUrl);
67          return parseChainedPublicKeyProviders(publicKeyBaseUrl);
68      }
69  
70      private KeyProvider<PublicKey> parseChainedPublicKeyProviders(String publicKeyBaseUrl) {
71          List<KeyProvider<PublicKey>> keyProviderChain = CHAIN_SPLITTER_REGEX.splitAsStream(publicKeyBaseUrl)
72                  .map(String::trim)
73                  .filter(StringUtils::isNotBlank)
74                  .map(this::parseMirroredPublicKeyProviders)
75                  .collect(Collectors.toList());
76  
77          if (keyProviderChain.isEmpty()) {
78              logger.warn("No key providers available, all requests will be rejected.");
79          }
80  
81          return ChainedKeyProvider.createChainedKeyProvider(keyProviderChain);
82      }
83  
84      private KeyProvider<PublicKey> parseMirroredPublicKeyProviders(String baseUrlComponent) {
85          List<KeyProvider<PublicKey>> mirroredProviders = MIRRORS_SPLITTER_REGEX.splitAsStream(baseUrlComponent)
86                  .map(String::trim)
87                  .filter(StringUtils::isNotBlank)
88                  .map(this::getPublicKeyKeyProvider)
89                  .collect(Collectors.toList());
90  
91          return MirroredKeyProvider.createMirroredKeyProvider(mirroredProviders);
92      }
93  
94      private KeyProvider<PublicKey> getPublicKeyKeyProvider(String baseUrl) {
95          URI parsedBaseUrl = URI.create(baseUrl);
96          checkArgument(parsedBaseUrl.isAbsolute(), "URL must be absolute");
97          checkArgument(!baseUrl.contains(","), "Comma must be encoded if present in URI");
98          checkArgument(!baseUrl.contains("|"), "Pipe must be encoded if present in URI");
99          switch (parsedBaseUrl.getScheme()) {
100             case "https":
101                 return new HttpPublicKeyProvider(parsedBaseUrl, httpClient, pemReader);
102             case "file":
103                 return new FilePublicKeyProvider(new File(parsedBaseUrl), pemReader);
104             case "classpath":
105                 return new ClasspathPublicKeyProvider(parsedBaseUrl.getPath(), pemReader);
106             default:
107                 throw new IllegalArgumentException("Unsupported public key server base URL protocol, " +
108                         "only https:, file: and classpath: are supported: " + parsedBaseUrl);
109         }
110     }
111 
112 }