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;
49 import static junit.framework.TestCase.*;
50 import static org.mockito.Matchers.eq;
51 import static org.mockito.Mockito.verify;
52 import static org.mockito.Mockito.when;
53 import static org.springframework.test.util.ReflectionTestUtils.setField;
55 class HttpTestServer {
57 List<String> requests = new ArrayList<>();
58 List<Integer> codes = new ArrayList<>();
59 List<String> respones = new ArrayList<>();
61 public void start() throws Exception {
66 private void startServer() throws Exception {
72 protected void configureServer() throws Exception {
73 Path jksPath = Paths.get(TestCbamTokenProvider.class.getResource("/unittests/localhost.jks").toURI());
74 String path = jksPath.normalize().toAbsolutePath().toUri().toString();
75 _server = new Server();
76 SslContextFactory factory = new SslContextFactory(path);
77 factory.setKeyStorePassword("changeit");
78 NetworkTrafficServerConnector connector = new NetworkTrafficServerConnector(_server, factory);
79 connector.setHost("127.0.0.1");
80 _server.addConnector(connector);
81 _server.setHandler(new AbstractHandler() {
83 public void handle(String target, org.eclipse.jetty.server.Request request, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws IOException, ServletException {
84 requests.add(new String(ByteStreams.toByteArray(request.getInputStream())));
85 httpServletResponse.getWriter().write(respones.remove(0));
86 httpServletResponse.setStatus(codes.remove(0));
87 request.setHandled(true);
92 public void stop() throws Exception {
97 public class TestCbamTokenProvider extends TestBase {
99 private static String GOOD_RESPONSE = "{ \"access_token\" : \"myToken\", \"expires_in\" : 1000 }";
101 private CbamTokenProvider cbamTokenProvider;
102 private VnfmInfo vnfmInfo = new VnfmInfo();
103 private ArgumentCaptor<SSLSocketFactory> sslSocketFactory = ArgumentCaptor.forClass(SSLSocketFactory.class);
104 private ArgumentCaptor<HostnameVerifier> hostnameVerifier = ArgumentCaptor.forClass(HostnameVerifier.class);
105 private HttpTestServer testServer;
108 public void initMocks() throws Exception {
109 setField(CbamTokenProvider.class, "logger", logger);
110 setField(cbamTokenProvider, "username", "myUserName");
111 setField(cbamTokenProvider, "password", "myPassword");
112 setField(cbamTokenProvider, "skipCertificateVerification", true);
113 setField(cbamTokenProvider, "skipHostnameVerification", true);
114 when(vnfmInfoProvider.getVnfmInfo(VNFM_ID)).thenReturn(vnfmInfo);
115 vnfmInfo.setPassword("vnfmPassword");
116 vnfmInfo.setUserName("vnfmUserName");
117 vnfmInfo.setUrl("http://127.0.0.3:12345");
118 testServer = new HttpTestServer();
120 URI uri = testServer._server.getURI();
121 setField(cbamTokenProvider, "cbamKeyCloakBaseUrl", uri.toString());
124 private void addGoodTokenResponse() {
125 testServer.respones.add(GOOD_RESPONSE);
126 testServer.codes.add(HttpStatus.OK.value());
130 public void testServer() throws Exception {
135 * a new token is requested no token has been requested before
138 public void testBasicTokenRequest() throws Exception {
140 addGoodTokenResponse();
142 String token = cbamTokenProvider.getToken(VNFM_ID);
144 assertEquals(1, testServer.requests.size());
145 assertTokenRequest(testServer.requests.get(0));
146 assertEquals("myToken", token);
151 * a new token is requested if the previous token has expired
154 public void testTokenIsRequestedIfPreviousExpired() throws Exception {
156 addGoodTokenResponse();
157 String firstToken = cbamTokenProvider.getToken(VNFM_ID);
158 testServer.respones.add("{ \"access_token\" : \"myToken2\", \"expires_in\" : 2000 }");
159 testServer.codes.add(HttpStatus.OK.value());
160 when(systemFunctions.currentTimeMillis()).thenReturn(500L * 1000 + 1L);
162 String token = cbamTokenProvider.getToken(VNFM_ID);
164 assertEquals(2, testServer.requests.size());
165 assertTokenRequest(testServer.requests.get(0));
166 assertTokenRequest(testServer.requests.get(1));
167 assertEquals("myToken2", token);
171 * a new token is not requested if the previous token has not expired
174 public void testTokenIsNotRequestedIfPreviousHasNotExpired() throws Exception {
176 addGoodTokenResponse();
177 String firstToken = cbamTokenProvider.getToken(VNFM_ID);
178 testServer.respones.add("{ \"access_token\" : \"myToken2\", \"expires_in\" : 2000 }");
179 testServer.codes.add(HttpStatus.OK.value());
180 when(systemFunctions.currentTimeMillis()).thenReturn(500L * 1000);
182 String token = cbamTokenProvider.getToken(VNFM_ID);
184 assertEquals(1, testServer.requests.size());
185 assertTokenRequest(testServer.requests.get(0));
186 assertEquals("myToken", token);
190 * failed token requests are retried for a fixed number amount of times
193 public void testRetry() throws Exception {
199 addGoodTokenResponse();
200 //cbamTokenProvider.failOnRequestNumber = 5;
202 String token = cbamTokenProvider.getToken(VNFM_ID);
204 assertEquals(5, testServer.requests.size());
205 assertTokenRequest(testServer.requests.get(0));
206 assertTokenRequest(testServer.requests.get(1));
207 assertTokenRequest(testServer.requests.get(2));
208 assertTokenRequest(testServer.requests.get(3));
209 assertTokenRequest(testServer.requests.get(4));
210 verify(logger).warn(eq("Unable to get token to access CBAM API (1/5)"), Mockito.<RuntimeException>any());
211 verify(logger).warn(eq("Unable to get token to access CBAM API (2/5)"), Mockito.<RuntimeException>any());
212 verify(logger).warn(eq("Unable to get token to access CBAM API (3/5)"), Mockito.<RuntimeException>any());
213 verify(logger).warn(eq("Unable to get token to access CBAM API (4/5)"), Mockito.<RuntimeException>any());
214 assertEquals("myToken", token);
218 * failed token requests are retried for a fixed number amount of times (reacing maximal number or retries)
221 public void testNoMoreRetry() throws Exception {
230 cbamTokenProvider.getToken(VNFM_ID);
232 } catch (RuntimeException e) {
233 assertNotNull(e.getCause());
236 assertEquals(5, testServer.requests.size());
237 assertTokenRequest(testServer.requests.get(0));
238 assertTokenRequest(testServer.requests.get(1));
239 assertTokenRequest(testServer.requests.get(2));
240 assertTokenRequest(testServer.requests.get(3));
241 assertTokenRequest(testServer.requests.get(4));
242 verify(logger).warn(eq("Unable to get token to access CBAM API (1/5)"), Mockito.<RuntimeException>any());
243 verify(logger).warn(eq("Unable to get token to access CBAM API (2/5)"), Mockito.<RuntimeException>any());
244 verify(logger).warn(eq("Unable to get token to access CBAM API (3/5)"), Mockito.<RuntimeException>any());
245 verify(logger).warn(eq("Unable to get token to access CBAM API (4/5)"), Mockito.<RuntimeException>any());
246 verify(logger).error(eq("Unable to get token to access CBAM API (giving up retries)"), Mockito.<RuntimeException>any());
249 private void addFailedResponse() {
250 testServer.codes.add(HttpStatus.UNAUTHORIZED.value());
251 testServer.respones.add(new String());
255 * the SSL connection is established without certificate & hostname verification
258 public void noSslVerification() throws Exception {
260 //the default settings is no SSL & hostname check
261 addGoodTokenResponse();
263 cbamTokenProvider.getToken(VNFM_ID);
265 //no exception is thrown
269 * if SSL is verified the certificates must be defined
272 public void testInvalidCombinationOfSettings() throws Exception {
274 setField(cbamTokenProvider, "skipCertificateVerification", false);
277 cbamTokenProvider.getToken(VNFM_ID);
280 } catch (RuntimeException e) {
281 assertEquals("If the skipCertificateVerification is set to false (default) the trustedCertificates can not be empty", e.getMessage());
286 * if SSL is verified the certificates must be defined
289 public void testInvalidCombinationOfSettings2() throws Exception {
291 setField(cbamTokenProvider, "skipCertificateVerification", false);
292 setField(cbamTokenProvider, "trustedCertificates", "xx\nxx");
295 cbamTokenProvider.getToken(VNFM_ID);
298 } catch (RuntimeException e) {
299 assertEquals("The trustedCertificates must be a base64 encoded collection of PEM certificates", e.getMessage());
300 assertNotNull(e.getCause());
305 * the SSL connection is established without certificate & hostname verification
308 public void testNotTrustedSslConnection() throws Exception {
310 setField(cbamTokenProvider, "skipCertificateVerification", false);
311 Path caPem = Paths.get(TestCbamTokenProvider.class.getResource("/unittests/sample.cert.pem").toURI());
312 setField(cbamTokenProvider, "trustedCertificates", Base64.getEncoder().encodeToString(Files.readAllBytes(caPem)));
313 addGoodTokenResponse();
316 cbamTokenProvider.getToken(VNFM_ID);
319 } catch (RuntimeException e) {
320 assertTrue(e.getCause().getCause().getMessage().contains("unable to find valid certification path"));
321 assertTrue(e.getCause() instanceof SSLHandshakeException);
326 * the SSL connection is established with certificate & hostname verification
329 public void testHostnameVerificationSucceeds() throws Exception {
331 setField(cbamTokenProvider, "skipCertificateVerification", false);
332 Path caPem = Paths.get(TestCbamTokenProvider.class.getResource("/unittests/localhost.cert.pem").toURI());
333 setField(cbamTokenProvider, "trustedCertificates", Base64.getEncoder().encodeToString(Files.readAllBytes(caPem)));
334 setField(cbamTokenProvider, "cbamKeyCloakBaseUrl", testServer._server.getURI().toString().replace("127.0.0.1", "localhost"));
335 setField(cbamTokenProvider, "skipHostnameVerification", false);
336 addGoodTokenResponse();
338 cbamTokenProvider.getToken(VNFM_ID);
340 //no seception is thrown
344 * the SSL connection is dropped with certificate & hostname verification due to invalid hostname
347 public void testHostnameverifcationfail() 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, "skipHostnameVerification", false);
353 addGoodTokenResponse();
356 cbamTokenProvider.getToken(VNFM_ID);
359 } catch (RuntimeException e) {
360 assertTrue(e.getCause().getMessage().contains("Hostname 127.0.0.1 not verified"));
361 assertTrue(e.getCause() instanceof SSLPeerUnverifiedException);
366 * invalid certificate content
369 public void testInvalidCerificateContent() throws Exception {
371 setField(cbamTokenProvider, "skipCertificateVerification", false);
372 setField(cbamTokenProvider, "trustedCertificates", Base64.getEncoder().encodeToString("-----BEGIN CERTIFICATE-----\nkuku\n-----END CERTIFICATE-----\n".getBytes()));
373 setField(cbamTokenProvider, "skipHostnameVerification", false);
374 addGoodTokenResponse();
377 cbamTokenProvider.getToken(VNFM_ID);
380 } catch (RuntimeException e) {
381 assertEquals("Unable to load certificates", e.getMessage());
382 assertTrue(e.getCause() instanceof GeneralSecurityException);
387 * Verify client certificates are not verified
391 public void testClientCertificates() throws Exception {
393 new CbamTokenProvider.AllTrustedTrustManager().checkClientTrusted(null, null);
395 //no security exception is thrown
399 * Exception during keystore creation is logged (semi-useless)
402 public void testKeystoreCreationFailure() {
403 KeyStoreException expectedException = new KeyStoreException();
404 class X extends CbamTokenProvider {
405 X(VnfmInfoProvider vnfmInfoProvider) {
406 super(vnfmInfoProvider);
410 TrustManager[] buildTrustManager() throws KeyStoreException {
411 throw expectedException;
415 new X(null).buildSSLSocketFactory();
417 } catch (RuntimeException e) {
418 assertEquals(expectedException, e.getCause());
419 verify(logger).error("Unable to create SSL socket factory", expectedException);
423 private void assertTokenRequest(String body) {
424 assertContains(body, "grant_type", "password");
425 assertContains(body, "client_id", "vnfmUserName");
426 assertContains(body, "client_secret", "vnfmPassword");
427 assertContains(body, "username", "myUserName");
428 assertContains(body, "password", "myPassword");
431 private void assertContains(String content, String key, String value) {
432 assertTrue(content.contains(key + "=" + value));