1 package com.atlassian.asap.core.keys.publickey;
2
3 import com.atlassian.asap.api.exception.CannotRetrieveKeyException;
4 import com.atlassian.asap.core.exception.PublicKeyNotFoundException;
5 import com.atlassian.asap.core.exception.PublicKeyRetrievalException;
6 import com.atlassian.asap.core.keys.PemReader;
7 import com.atlassian.asap.core.validator.ValidatedKeyId;
8 import org.apache.http.HttpEntity;
9 import org.apache.http.HttpHeaders;
10 import org.apache.http.HttpRequestInterceptor;
11 import org.apache.http.HttpResponse;
12 import org.apache.http.StatusLine;
13 import org.apache.http.client.HttpClient;
14 import org.apache.http.client.methods.HttpGet;
15 import org.apache.http.message.BasicHeader;
16 import org.junit.Before;
17 import org.junit.Test;
18 import org.junit.runner.RunWith;
19 import org.mockito.ArgumentCaptor;
20 import org.mockito.Captor;
21 import org.mockito.Mock;
22 import org.mockito.runners.MockitoJUnitRunner;
23
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.InputStreamReader;
27 import java.io.Reader;
28 import java.net.URI;
29 import java.security.PublicKey;
30 import java.security.interfaces.RSAPublicKey;
31
32 import static org.junit.Assert.assertEquals;
33 import static org.mockito.Matchers.any;
34 import static org.mockito.Mockito.verify;
35 import static org.mockito.Mockito.when;
36
37 @RunWith(MockitoJUnitRunner.class)
38 public class HttpPublicKeyProviderTest {
39 private static final BasicHeader CONTENT_TYPE_X_PEM_FILE = new BasicHeader("Content-Type", "application/x-pem-file");
40 private static final URI BASE_URL = URI.create("https://example.test/");
41
42 @Mock
43 private PemReader pemReader;
44
45 @Mock
46 private HttpClient httpClient;
47
48 @Mock
49 private HttpResponse response;
50
51 @Mock
52 private HttpEntity entity;
53
54 @Mock
55 private StatusLine statusLine;
56
57 @Mock
58 private InputStream inputStream;
59
60 @Mock
61 private RSAPublicKey publicKey;
62
63 @Captor
64 private ArgumentCaptor<HttpGet> httpGetCaptor;
65
66 private HttpPublicKeyProvider publicKeyProvider;
67
68 @Before
69 public void setUp() throws Exception {
70 publicKeyProvider = new HttpPublicKeyProvider(BASE_URL, httpClient, pemReader);
71 when(response.getEntity()).thenReturn(entity);
72 when(response.getStatusLine()).thenReturn(statusLine);
73 when(entity.getContent()).thenReturn(inputStream);
74
75 }
76
77 @Test(expected = PublicKeyRetrievalException.class)
78 public void shouldFailIfHttpRequestFails() throws Exception {
79 when(httpClient.execute(any(HttpGet.class))).thenThrow(new IOException("Error retrieving public key"));
80
81 publicKeyProvider.getKey(ValidatedKeyId.validate("some-key-id"));
82 }
83
84 @Test(expected = PublicKeyRetrievalException.class)
85 public void shouldFailIfKeyRepoReturnsInternalError() throws Exception {
86 when(httpClient.execute(any(HttpGet.class))).thenReturn(response);
87 when(response.getEntity()).thenReturn(null);
88 when(statusLine.getStatusCode()).thenReturn(500);
89
90 publicKeyProvider.getKey(ValidatedKeyId.validate("some-key-id"));
91 }
92
93 @Test(expected = PublicKeyNotFoundException.class)
94 public void shouldReturnNoneIfPublicKeyIsNotFound() throws Exception {
95 when(httpClient.execute(any(HttpGet.class))).thenReturn(response);
96 when(response.getEntity()).thenReturn(null);
97 when(statusLine.getStatusCode()).thenReturn(404);
98
99 publicKeyProvider.getKey(ValidatedKeyId.validate("some-key-id"));
100 }
101
102 @Test(expected = CannotRetrieveKeyException.class)
103 public void shouldFailIfPublicKeyDoesNotParse() throws Exception {
104 when(httpClient.execute(any(HttpGet.class))).thenReturn(response);
105 when(statusLine.getStatusCode()).thenReturn(200);
106 when(entity.getContentType()).thenReturn(CONTENT_TYPE_X_PEM_FILE);
107 when(pemReader.readPublicKey(any(Reader.class))).thenThrow(CannotRetrieveKeyException.class);
108
109 publicKeyProvider.getKey(ValidatedKeyId.validate("some-key-id"));
110 }
111
112 @Test(expected = CannotRetrieveKeyException.class)
113 public void shouldFailIfContentTypeIsNotPem() throws Exception {
114 when(httpClient.execute(any(HttpGet.class))).thenReturn(response);
115 when(statusLine.getStatusCode()).thenReturn(200);
116 when(entity.getContentType()).thenReturn(new BasicHeader("Content-Type", "application/not-pem"));
117
118 publicKeyProvider.getKey(ValidatedKeyId.validate("some-key-id"));
119 }
120
121 @Test
122 public void shouldReturnParsedPublicKey() throws Exception {
123 when(httpClient.execute(any(HttpGet.class))).thenReturn(response);
124 when(statusLine.getStatusCode()).thenReturn(200);
125 when(entity.getContentType()).thenReturn(CONTENT_TYPE_X_PEM_FILE);
126 when(pemReader.readPublicKey(any(InputStreamReader.class))).thenReturn(publicKey);
127
128 PublicKey pk = publicKeyProvider.getKey(ValidatedKeyId.validate("some-key-id"));
129 assertEquals(publicKey, pk);
130 }
131
132 @Test
133 public void shouldIncludeAcceptHeaderInTheRequest() throws Exception {
134 when(httpClient.execute(any(HttpGet.class))).thenReturn(response);
135 when(statusLine.getStatusCode()).thenReturn(200);
136 when(entity.getContentType()).thenReturn(CONTENT_TYPE_X_PEM_FILE);
137 when(pemReader.readPublicKey(any(InputStreamReader.class))).thenReturn(publicKey);
138
139 publicKeyProvider.getKey(ValidatedKeyId.validate("some-key-id"));
140
141 verify(httpClient).execute(httpGetCaptor.capture());
142 assertEquals(HttpPublicKeyProvider.ACCEPT_HEADER_VALUE,
143 httpGetCaptor.getValue().getFirstHeader(HttpHeaders.ACCEPT).getValue());
144 }
145
146 @Test
147 public void shouldComposeTheBaseUrlAndTheKeyId() throws Exception {
148 when(httpClient.execute(any(HttpGet.class))).thenReturn(response);
149 when(statusLine.getStatusCode()).thenReturn(200);
150 when(entity.getContentType()).thenReturn(CONTENT_TYPE_X_PEM_FILE);
151 when(pemReader.readPublicKey(any(InputStreamReader.class))).thenReturn(publicKey);
152
153 publicKeyProvider.getKey(ValidatedKeyId.validate("some-key-id"));
154
155 verify(httpClient).execute(httpGetCaptor.capture());
156 assertEquals(URI.create(BASE_URL + "some-key-id"), httpGetCaptor.getValue().getURI());
157 }
158
159 @Test(expected = IllegalArgumentException.class)
160 public void shouldRejectSchemelessBaseUrl() throws Exception {
161 new HttpPublicKeyProvider(URI.create("///bar/"), httpClient, pemReader);
162 }
163
164 @Test(expected = IllegalArgumentException.class)
165 public void shouldRejectSchemesOtherThanHttps() throws Exception {
166 new HttpPublicKeyProvider(URI.create("file:///bar/"), httpClient, pemReader);
167 }
168
169 @Test(expected = IllegalArgumentException.class)
170 public void shouldRejectInsecureHttp() throws Exception {
171 new HttpPublicKeyProvider(URI.create("http://example.test/"), httpClient, pemReader);
172 }
173
174 @Test(expected = IllegalArgumentException.class)
175 public void shouldRejectBaseUrlWithoutTrailingSlash() throws Exception {
176 new HttpPublicKeyProvider(URI.create("https://example.test/bar"), httpClient, pemReader);
177 }
178
179 @Test
180 public void shouldAllowExtendingHttpClient() {
181
182 HttpRequestInterceptor interceptor = (request, context) -> {
183
184 };
185 HttpClient client = HttpPublicKeyProvider.defaultHttpClientBuilder()
186 .addInterceptorFirst(interceptor)
187 .build();
188 new HttpPublicKeyProvider(BASE_URL, client, pemReader);
189 }
190 }