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.util.ArrayList;
46 import java.util.Base64;
47 import java.util.List;
48 import java.util.concurrent.ExecutorService;
49 import java.util.concurrent.Executors;
50 import java.util.concurrent.Future;
51 import java.util.concurrent.TimeUnit;
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 volatile List<String> requests = new ArrayList<>();
62 volatile List<Integer> codes = new ArrayList<>();
63 volatile List<String> respones = new ArrayList<>();
64 ExecutorService executorService = Executors.newCachedThreadPool();
65 public void start() throws Exception {
70 private void startServer() throws Exception {
74 Future<?> serverStarted = executorService.submit(() -> {
78 if(_server.isStarted()){
81 } catch (InterruptedException e) {
85 serverStarted.get(30, TimeUnit.SECONDS);
88 protected void configureServer() throws Exception {
89 Path jksPath = Paths.get(TestCbamTokenProvider.class.getResource("/unittests/localhost.jks").toURI());
90 String path = jksPath.normalize().toAbsolutePath().toUri().toString();
91 _server = new Server();
92 SslContextFactory factory = new SslContextFactory(path);
93 factory.setKeyStorePassword("changeit");
94 NetworkTrafficServerConnector connector = new NetworkTrafficServerConnector(_server, factory);
95 connector.setHost("127.0.0.1");
96 _server.addConnector(connector);
97 _server.setHandler(new AbstractHandler() {
99 public void handle(String target, org.eclipse.jetty.server.Request request, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException, ServletException {
100 requests.add(new String(ByteStreams.toByteArray(request.getInputStream())));
101 httpServletResponse.getWriter().write(respones.remove(0));
102 httpServletResponse.setStatus(codes.remove(0));
103 request.setHandled(true);
108 public void stop() throws Exception {
113 public class TestCbamTokenProvider extends TestBase {
115 private static String GOOD_RESPONSE = "{ \"access_token\" : \"myToken\", \"expires_in\" : 1000 }";
117 private CbamTokenProvider cbamTokenProvider;
118 private VnfmInfo vnfmInfo = new VnfmInfo();
119 private ArgumentCaptor<SSLSocketFactory> sslSocketFactory = ArgumentCaptor.forClass(SSLSocketFactory.class);
120 private ArgumentCaptor<HostnameVerifier> hostnameVerifier = ArgumentCaptor.forClass(HostnameVerifier.class);
121 private HttpTestServer testServer;
124 public void initMocks() throws Exception {
125 setField(CbamTokenProvider.class, "logger", logger);
126 setField(cbamTokenProvider, "username", "myUserName");
127 setField(cbamTokenProvider, "password", "myPassword");
128 setField(cbamTokenProvider, "skipCertificateVerification", true);
129 setField(cbamTokenProvider, "skipHostnameVerification", true);
130 when(vnfmInfoProvider.getVnfmInfo(VNFM_ID)).thenReturn(vnfmInfo);
131 vnfmInfo.setPassword("vnfmPassword");
132 vnfmInfo.setUserName("vnfmUserName");
133 vnfmInfo.setUrl("http://127.0.0.3:12345");
134 testServer = new HttpTestServer();
136 URI uri = testServer._server.getURI();
137 setField(cbamTokenProvider, "cbamKeyCloakBaseUrl", uri.toString());
142 private void addGoodTokenResponse() {
143 testServer.respones.add(GOOD_RESPONSE);
144 testServer.codes.add(HttpStatus.OK.value());
148 public void testServer() throws Exception {
153 * a new token is requested no token has been requested before
156 public void testBasicTokenRequest() throws Exception {
158 addGoodTokenResponse();
160 String token = cbamTokenProvider.getToken(VNFM_ID);
162 assertEquals(1, testServer.requests.size());
163 assertTokenRequest(testServer.requests.get(0));
164 assertEquals("myToken", token);
169 * a new token is requested if the previous token has expired
172 public void testTokenIsRequestedIfPreviousExpired() throws Exception {
174 addGoodTokenResponse();
175 String firstToken = cbamTokenProvider.getToken(VNFM_ID);
176 testServer.respones.add("{ \"access_token\" : \"myToken2\", \"expires_in\" : 2000 }");
177 testServer.codes.add(HttpStatus.OK.value());
178 when(systemFunctions.currentTimeMillis()).thenReturn(500L * 1000 + 1L);
180 String token = cbamTokenProvider.getToken(VNFM_ID);
182 assertEquals(2, testServer.requests.size());
183 assertTokenRequest(testServer.requests.get(0));
184 assertTokenRequest(testServer.requests.get(1));
185 assertEquals("myToken2", token);
189 * a new token is not requested if the previous token has not expired
192 public void testTokenIsNotRequestedIfPreviousHasNotExpired() throws Exception {
194 addGoodTokenResponse();
195 String firstToken = cbamTokenProvider.getToken(VNFM_ID);
196 testServer.respones.add("{ \"access_token\" : \"myToken2\", \"expires_in\" : 2000 }");
197 testServer.codes.add(HttpStatus.OK.value());
198 when(systemFunctions.currentTimeMillis()).thenReturn(500L * 1000);
200 String token = cbamTokenProvider.getToken(VNFM_ID);
202 assertEquals(1, testServer.requests.size());
203 assertTokenRequest(testServer.requests.get(0));
204 assertEquals("myToken", token);
208 * failed token requests are retried for a fixed number amount of times
211 public void testRetry() throws Exception {
217 addGoodTokenResponse();
218 //cbamTokenProvider.failOnRequestNumber = 5;
220 String token = cbamTokenProvider.getToken(VNFM_ID);
222 assertEquals(5, testServer.requests.size());
223 assertTokenRequest(testServer.requests.get(0));
224 assertTokenRequest(testServer.requests.get(1));
225 assertTokenRequest(testServer.requests.get(2));
226 assertTokenRequest(testServer.requests.get(3));
227 assertTokenRequest(testServer.requests.get(4));
228 verify(logger).warn(eq("Unable to get token to access CBAM API (1/5)"), Mockito.<RuntimeException>any());
229 verify(logger).warn(eq("Unable to get token to access CBAM API (2/5)"), Mockito.<RuntimeException>any());
230 verify(logger).warn(eq("Unable to get token to access CBAM API (3/5)"), Mockito.<RuntimeException>any());
231 verify(logger).warn(eq("Unable to get token to access CBAM API (4/5)"), Mockito.<RuntimeException>any());
232 assertEquals("myToken", token);
236 * failed token requests are retried for a fixed number amount of times (reacing maximal number or retries)
239 public void testNoMoreRetry() throws Exception {
248 cbamTokenProvider.getToken(VNFM_ID);
250 } catch (RuntimeException e) {
251 assertNotNull(e.getCause());
254 assertEquals(5, testServer.requests.size());
255 assertTokenRequest(testServer.requests.get(0));
256 assertTokenRequest(testServer.requests.get(1));
257 assertTokenRequest(testServer.requests.get(2));
258 assertTokenRequest(testServer.requests.get(3));
259 assertTokenRequest(testServer.requests.get(4));
260 verify(logger).warn(eq("Unable to get token to access CBAM API (1/5)"), Mockito.<RuntimeException>any());
261 verify(logger).warn(eq("Unable to get token to access CBAM API (2/5)"), Mockito.<RuntimeException>any());
262 verify(logger).warn(eq("Unable to get token to access CBAM API (3/5)"), Mockito.<RuntimeException>any());
263 verify(logger).warn(eq("Unable to get token to access CBAM API (4/5)"), Mockito.<RuntimeException>any());
264 verify(logger).error(eq("Unable to get token to access CBAM API (giving up retries)"), Mockito.<RuntimeException>any());
267 private void addFailedResponse() {
268 testServer.codes.add(HttpStatus.UNAUTHORIZED.value());
269 testServer.respones.add(new String());
273 * the SSL connection is established without certificate & hostname verification
276 public void noSslVerification() throws Exception {
278 //the default settings is no SSL & hostname check
279 addGoodTokenResponse();
281 cbamTokenProvider.getToken(VNFM_ID);
283 //no exception is thrown
287 * if SSL is verified the certificates must be defined
290 public void testInvalidCombinationOfSettings() throws Exception {
292 setField(cbamTokenProvider, "skipCertificateVerification", false);
295 cbamTokenProvider.getToken(VNFM_ID);
298 } catch (RuntimeException e) {
299 assertEquals("If the skipCertificateVerification is set to false (default) the trustedCertificates can not be empty", e.getMessage());
304 * if SSL is verified the certificates must be defined
307 public void testInvalidCombinationOfSettings2() throws Exception {
309 setField(cbamTokenProvider, "skipCertificateVerification", false);
310 setField(cbamTokenProvider, "trustedCertificates", "xx\nxx");
313 cbamTokenProvider.getToken(VNFM_ID);
316 } catch (RuntimeException e) {
317 assertEquals("The trustedCertificates must be a base64 encoded collection of PEM certificates", e.getMessage());
318 assertNotNull(e.getCause());
323 * the SSL connection is established without certificate & hostname verification
326 public void testNotTrustedSslConnection() throws Exception {
328 setField(cbamTokenProvider, "skipCertificateVerification", false);
329 Path caPem = Paths.get(TestCbamTokenProvider.class.getResource("/unittests/sample.cert.pem").toURI());
330 setField(cbamTokenProvider, "trustedCertificates", Base64.getEncoder().encodeToString(Files.readAllBytes(caPem)));
331 addGoodTokenResponse();
334 cbamTokenProvider.getToken(VNFM_ID);
337 } catch (RuntimeException e) {
338 assertTrue(e.getCause().getCause().getMessage().contains("unable to find valid certification path"));
339 assertTrue(e.getCause() instanceof SSLHandshakeException);
344 * the SSL connection is established with certificate & hostname verification
347 public void testHostnameVerificationSucceeds() throws Exception {
349 setField(cbamTokenProvider, "skipCertificateVerification", false);
350 Path caPem = Paths.get(TestCbamTokenProvider.class.getResource("/unittests/localhost.cert.pem").toURI());
351 setField(cbamTokenProvider, "trustedCertificates", Base64.getEncoder().encodeToString(Files.readAllBytes(caPem)));
352 setField(cbamTokenProvider, "cbamKeyCloakBaseUrl", testServer._server.getURI().toString().replace("127.0.0.1", "localhost"));
353 setField(cbamTokenProvider, "skipHostnameVerification", false);
354 addGoodTokenResponse();
356 cbamTokenProvider.getToken(VNFM_ID);
358 //no seception is thrown
362 * the SSL connection is dropped with certificate & hostname verification due to invalid hostname
365 public void testHostnameverifcationfail() throws Exception {
367 setField(cbamTokenProvider, "skipCertificateVerification", false);
368 Path caPem = Paths.get(TestCbamTokenProvider.class.getResource("/unittests/localhost.cert.pem").toURI());
369 setField(cbamTokenProvider, "trustedCertificates", Base64.getEncoder().encodeToString(Files.readAllBytes(caPem)));
370 setField(cbamTokenProvider, "skipHostnameVerification", false);
371 addGoodTokenResponse();
374 cbamTokenProvider.getToken(VNFM_ID);
377 } catch (RuntimeException e) {
378 assertTrue(e.getCause().getMessage().contains("Hostname 127.0.0.1 not verified"));
379 assertTrue(e.getCause() instanceof SSLPeerUnverifiedException);
384 * invalid certificate content
387 public void testInvalidCerificateContent() throws Exception {
389 setField(cbamTokenProvider, "skipCertificateVerification", false);
390 setField(cbamTokenProvider, "trustedCertificates", Base64.getEncoder().encodeToString("-----BEGIN CERTIFICATE-----\nkuku\n-----END CERTIFICATE-----\n".getBytes()));
391 setField(cbamTokenProvider, "skipHostnameVerification", false);
392 addGoodTokenResponse();
395 cbamTokenProvider.getToken(VNFM_ID);
398 } catch (RuntimeException e) {
399 assertEquals("Unable to load certificates", e.getMessage());
400 assertTrue(e.getCause() instanceof GeneralSecurityException);
405 * Verify client certificates are not verified
409 public void testClientCertificates() throws Exception {
411 new CbamTokenProvider.AllTrustedTrustManager().checkClientTrusted(null, null);
413 //no security exception is thrown
417 * Exception during keystore creation is logged (semi-useless)
420 public void testKeystoreCreationFailure() {
421 KeyStoreException expectedException = new KeyStoreException();
422 class X extends CbamTokenProvider {
423 X(VnfmInfoProvider vnfmInfoProvider) {
424 super(vnfmInfoProvider);
428 TrustManager[] buildTrustManager() throws KeyStoreException {
429 throw expectedException;
433 new X(null).buildSSLSocketFactory();
435 } catch (RuntimeException e) {
436 assertEquals(expectedException, e.getCause());
437 verify(logger).error("Unable to create SSL socket factory", expectedException);
441 private void assertTokenRequest(String body) {
442 assertContains(body, "grant_type", "password");
443 assertContains(body, "client_id", "vnfmUserName");
444 assertContains(body, "client_secret", "vnfmPassword");
445 assertContains(body, "username", "myUserName");
446 assertContains(body, "password", "myPassword");
449 private void assertContains(String content, String key, String value) {
450 assertTrue(content.contains(key + "=" + value));