2 * Copyright 2016-2017, Nokia Corporation
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm;
19 import com.google.common.io.ByteStreams;
20 import org.eclipse.jetty.server.NetworkTrafficServerConnector;
21 import org.eclipse.jetty.server.Server;
22 import org.eclipse.jetty.server.handler.AbstractHandler;
23 import org.eclipse.jetty.util.ssl.SslContextFactory;
24 import org.junit.After;
25 import org.junit.Before;
26 import org.junit.Test;
27 import org.mockito.ArgumentCaptor;
28 import org.mockito.InjectMocks;
29 import org.mockito.Mockito;
30 import org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.api.VnfmInfoProvider;
31 import org.onap.vnfmdriver.model.VnfmInfo;
32 import org.springframework.http.HttpStatus;
34 import javax.net.ssl.*;
35 import javax.servlet.ServletException;
36 import javax.servlet.http.HttpServletRequest;
37 import javax.servlet.http.HttpServletResponse;
38 import java.io.IOException;
40 import java.nio.file.Files;
41 import java.nio.file.Path;
42 import java.nio.file.Paths;
43 import java.security.GeneralSecurityException;
44 import java.security.KeyStoreException;
45 import java.security.NoSuchAlgorithmException;
46 import java.security.NoSuchProviderException;
47 import java.security.cert.CertificateException;
48 import java.security.spec.InvalidKeySpecException;
49 import java.util.ArrayList;
50 import java.util.Base64;
51 import java.util.List;
53 import static junit.framework.TestCase.*;
54 import static org.mockito.Matchers.eq;
55 import static org.mockito.Mockito.verify;
56 import static org.mockito.Mockito.when;
57 import static org.springframework.test.util.ReflectionTestUtils.setField;
59 class HttpTestServer {
61 List<String> requests = new ArrayList<>();
62 List<Integer> codes = new ArrayList<>();
63 List<String> respones = new ArrayList<>();
65 public void start() throws Exception {
70 private void startServer() throws Exception {
76 protected void configureServer() throws Exception {
77 Path jksPath = Paths.get(TestCbamTokenProvider.class.getResource("/unittests/localhost.jks").toURI());
78 String path = jksPath.normalize().toAbsolutePath().toUri().toString();
79 _server = new Server();
80 SslContextFactory factory = new SslContextFactory(path);
81 factory.setKeyStorePassword("changeit");
82 NetworkTrafficServerConnector connector = new NetworkTrafficServerConnector(_server, factory);
83 connector.setHost("127.0.0.1");
84 _server.addConnector(connector);
85 _server.setHandler(new AbstractHandler() {
87 public void handle(String target, org.eclipse.jetty.server.Request request, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException, ServletException {
88 requests.add(new String(ByteStreams.toByteArray(request.getInputStream())));
89 httpServletResponse.getWriter().write(respones.remove(0));
90 httpServletResponse.setStatus(codes.remove(0));
91 request.setHandled(true);
96 public void stop() throws Exception {
101 public class TestCbamTokenProvider extends TestBase {
103 private static String GOOD_RESPONSE = "{ \"access_token\" : \"myToken\", \"expires_in\" : 1000 }";
105 private CbamTokenProvider cbamTokenProvider;
106 private VnfmInfo vnfmInfo = new VnfmInfo();
107 private ArgumentCaptor<SSLSocketFactory> sslSocketFactory = ArgumentCaptor.forClass(SSLSocketFactory.class);
108 private ArgumentCaptor<HostnameVerifier> hostnameVerifier = ArgumentCaptor.forClass(HostnameVerifier.class);
109 private HttpTestServer testServer;
112 public void initMocks() throws Exception {
113 setField(CbamTokenProvider.class, "logger", logger);
114 setField(cbamTokenProvider, "username", "myUserName");
115 setField(cbamTokenProvider, "password", "myPassword");
116 setField(cbamTokenProvider, "skipCertificateVerification", true);
117 setField(cbamTokenProvider, "skipHostnameVerification", true);
118 when(vnfmInfoProvider.getVnfmInfo(VNFM_ID)).thenReturn(vnfmInfo);
119 vnfmInfo.setPassword("vnfmPassword");
120 vnfmInfo.setUserName("vnfmUserName");
121 vnfmInfo.setUrl("http://127.0.0.3:12345");
122 testServer = new HttpTestServer();
124 URI uri = testServer._server.getURI();
125 setField(cbamTokenProvider, "cbamKeyCloakBaseUrl", uri.toString());
128 private void addGoodTokenResponse() {
129 testServer.respones.add(GOOD_RESPONSE);
130 testServer.codes.add(HttpStatus.OK.value());
134 public void testServer() throws Exception {
139 * a new token is requested no token has been requested before
142 public void testBasicTokenRequest() throws Exception {
144 addGoodTokenResponse();
146 String token = cbamTokenProvider.getToken(VNFM_ID);
148 assertEquals(1, testServer.requests.size());
149 assertTokenRequest(testServer.requests.get(0));
150 assertEquals("myToken", token);
155 * a new token is requested if the previous token has expired
158 public void testTokenIsRequestedIfPreviousExpired() throws Exception {
160 addGoodTokenResponse();
161 String firstToken = cbamTokenProvider.getToken(VNFM_ID);
162 testServer.respones.add("{ \"access_token\" : \"myToken2\", \"expires_in\" : 2000 }");
163 testServer.codes.add(HttpStatus.OK.value());
164 when(systemFunctions.currentTimeMillis()).thenReturn(500L * 1000 + 1L);
166 String token = cbamTokenProvider.getToken(VNFM_ID);
168 assertEquals(2, testServer.requests.size());
169 assertTokenRequest(testServer.requests.get(0));
170 assertTokenRequest(testServer.requests.get(1));
171 assertEquals("myToken2", token);
175 * a new token is not requested if the previous token has not expired
178 public void testTokenIsNotRequestedIfPreviousHasNotExpired() throws Exception {
180 addGoodTokenResponse();
181 String firstToken = cbamTokenProvider.getToken(VNFM_ID);
182 testServer.respones.add("{ \"access_token\" : \"myToken2\", \"expires_in\" : 2000 }");
183 testServer.codes.add(HttpStatus.OK.value());
184 when(systemFunctions.currentTimeMillis()).thenReturn(500L * 1000);
186 String token = cbamTokenProvider.getToken(VNFM_ID);
188 assertEquals(1, testServer.requests.size());
189 assertTokenRequest(testServer.requests.get(0));
190 assertEquals("myToken", token);
194 * failed token requests are retried for a fixed number amount of times
197 public void testRetry() throws Exception {
203 addGoodTokenResponse();
204 //cbamTokenProvider.failOnRequestNumber = 5;
206 String token = cbamTokenProvider.getToken(VNFM_ID);
208 assertEquals(5, testServer.requests.size());
209 assertTokenRequest(testServer.requests.get(0));
210 assertTokenRequest(testServer.requests.get(1));
211 assertTokenRequest(testServer.requests.get(2));
212 assertTokenRequest(testServer.requests.get(3));
213 assertTokenRequest(testServer.requests.get(4));
214 verify(logger).warn(eq("Unable to get token to access CBAM API (1/5)"), Mockito.<RuntimeException>any());
215 verify(logger).warn(eq("Unable to get token to access CBAM API (2/5)"), Mockito.<RuntimeException>any());
216 verify(logger).warn(eq("Unable to get token to access CBAM API (3/5)"), Mockito.<RuntimeException>any());
217 verify(logger).warn(eq("Unable to get token to access CBAM API (4/5)"), Mockito.<RuntimeException>any());
218 assertEquals("myToken", token);
222 * failed token requests are retried for a fixed number amount of times (reacing maximal number or retries)
225 public void testNoMoreRetry() throws Exception {
234 cbamTokenProvider.getToken(VNFM_ID);
236 } catch (RuntimeException e) {
237 assertNotNull(e.getCause());
240 assertEquals(5, testServer.requests.size());
241 assertTokenRequest(testServer.requests.get(0));
242 assertTokenRequest(testServer.requests.get(1));
243 assertTokenRequest(testServer.requests.get(2));
244 assertTokenRequest(testServer.requests.get(3));
245 assertTokenRequest(testServer.requests.get(4));
246 verify(logger).warn(eq("Unable to get token to access CBAM API (1/5)"), Mockito.<RuntimeException>any());
247 verify(logger).warn(eq("Unable to get token to access CBAM API (2/5)"), Mockito.<RuntimeException>any());
248 verify(logger).warn(eq("Unable to get token to access CBAM API (3/5)"), Mockito.<RuntimeException>any());
249 verify(logger).warn(eq("Unable to get token to access CBAM API (4/5)"), Mockito.<RuntimeException>any());
250 verify(logger).error(eq("Unable to get token to access CBAM API (giving up retries)"), Mockito.<RuntimeException>any());
253 private void addFailedResponse() {
254 testServer.codes.add(HttpStatus.UNAUTHORIZED.value());
255 testServer.respones.add(new String());
259 * the SSL connection is established without certificate & hostname verification
262 public void noSslVerification() throws Exception {
264 //the default settings is no SSL & hostname check
265 addGoodTokenResponse();
267 cbamTokenProvider.getToken(VNFM_ID);
269 //no exception is thrown
273 * if SSL is verified the certificates must be defined
276 public void testInvalidCombinationOfSettings() throws Exception {
278 setField(cbamTokenProvider, "skipCertificateVerification", false);
281 cbamTokenProvider.getToken(VNFM_ID);
284 } catch (RuntimeException e) {
285 assertEquals("If the skipCertificateVerification is set to false (default) the trustedCertificates can not be empty", e.getMessage());
290 * if SSL is verified the certificates must be defined
293 public void testInvalidCombinationOfSettings2() throws Exception {
295 setField(cbamTokenProvider, "skipCertificateVerification", false);
296 setField(cbamTokenProvider, "trustedCertificates", "xx\nxx");
299 cbamTokenProvider.getToken(VNFM_ID);
302 } catch (RuntimeException e) {
303 assertEquals("The trustedCertificates must be a base64 encoded collection of PEM certificates", e.getMessage());
304 assertNotNull(e.getCause());
309 * the SSL connection is established without certificate & hostname verification
312 public void testNotTrustedSslConnection() throws Exception {
314 setField(cbamTokenProvider, "skipCertificateVerification", false);
315 Path caPem = Paths.get(TestCbamTokenProvider.class.getResource("/unittests/sample.cert.pem").toURI());
316 setField(cbamTokenProvider, "trustedCertificates", Base64.getEncoder().encodeToString(Files.readAllBytes(caPem)));
317 addGoodTokenResponse();
320 cbamTokenProvider.getToken(VNFM_ID);
323 } catch (RuntimeException e) {
324 assertTrue(e.getMessage().contains("unable to find valid certification path"));
325 assertTrue(e.getCause() instanceof SSLHandshakeException);
330 * the SSL connection is established with certificate & hostname verification
333 public void testHostnameVerificationSucceeds() throws Exception {
335 setField(cbamTokenProvider, "skipCertificateVerification", false);
336 Path caPem = Paths.get(TestCbamTokenProvider.class.getResource("/unittests/localhost.cert.pem").toURI());
337 setField(cbamTokenProvider, "trustedCertificates", Base64.getEncoder().encodeToString(Files.readAllBytes(caPem)));
338 setField(cbamTokenProvider, "cbamKeyCloakBaseUrl", testServer._server.getURI().toString().replace("127.0.0.1", "localhost"));
339 setField(cbamTokenProvider, "skipHostnameVerification", false);
340 addGoodTokenResponse();
342 cbamTokenProvider.getToken(VNFM_ID);
344 //no seception is thrown
348 * the SSL connection is dropped with certificate & hostname verification due to invalid hostname
351 public void testHostnameverifcationfail() throws Exception {
353 setField(cbamTokenProvider, "skipCertificateVerification", false);
354 Path caPem = Paths.get(TestCbamTokenProvider.class.getResource("/unittests/localhost.cert.pem").toURI());
355 setField(cbamTokenProvider, "trustedCertificates", Base64.getEncoder().encodeToString(Files.readAllBytes(caPem)));
356 setField(cbamTokenProvider, "skipHostnameVerification", false);
357 addGoodTokenResponse();
360 cbamTokenProvider.getToken(VNFM_ID);
363 } catch (RuntimeException e) {
364 assertTrue(e.getMessage().contains("Hostname 127.0.0.1 not verified"));
365 assertTrue(e.getCause() instanceof SSLPeerUnverifiedException);
370 * invalid certificate content
373 public void testInvalidCerificateContent() throws Exception {
375 setField(cbamTokenProvider, "skipCertificateVerification", false);
376 setField(cbamTokenProvider, "trustedCertificates", Base64.getEncoder().encodeToString("-----BEGIN CERTIFICATE-----\nkuku\n-----END CERTIFICATE-----\n".getBytes()));
377 setField(cbamTokenProvider, "skipHostnameVerification", false);
378 addGoodTokenResponse();
381 cbamTokenProvider.getToken(VNFM_ID);
384 } catch (RuntimeException e) {
385 assertTrue(e.getMessage().contains("Unable to load certificates"));
386 assertTrue(e.getCause() instanceof GeneralSecurityException);
391 * Verify client certificates are not verified
395 public void testClientCertificates() throws Exception {
397 new CbamTokenProvider.AllTrustedTrustManager().checkClientTrusted(null, null);
399 //no security exception is thrown
403 * Exception during keystore creation is logged (semi-useless)
406 public void testKeystoreCreationFailure() {
407 KeyStoreException expectedException = new KeyStoreException();
408 class X extends CbamTokenProvider {
409 X(VnfmInfoProvider vnfmInfoProvider) {
410 super(vnfmInfoProvider);
414 TrustManager[] buildTrustManager() throws KeyStoreException, NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException, CertificateException {
415 throw expectedException;
419 new X(null).buildSSLSocketFactory();
421 } catch (RuntimeException e) {
422 assertEquals(expectedException, e.getCause());
423 verify(logger).error("Unable to create SSL socket factory", expectedException);
427 private void assertTokenRequest(String body) {
428 assertContains(body, "grant_type", "password");
429 assertContains(body, "client_id", "vnfmUserName");
430 assertContains(body, "client_secret", "vnfmPassword");
431 assertContains(body, "username", "myUserName");
432 assertContains(body, "password", "myPassword");
435 private void assertContains(String content, String key, String value) {
436 assertTrue(content.contains(key + "=" + value));