Updating Nokia driver
[vfc/nfvo/driver/vnfm/svnfm.git] / nokiav2 / driver / src / test / java / org / onap / vfc / nfvo / driver / vnfm / svnfm / nokia / vnfm / TestCbamTokenProvider.java
1 /*
2  * Copyright 2016-2017, Nokia Corporation
3  *
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
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package org.onap.vfc.nfvo.driver.vnfm.svnfm.nokia.vnfm;
18
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;
33
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;
39 import java.net.URI;
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;
52
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;
58
59 class HttpTestServer {
60     Server _server;
61     List<String> requests = new ArrayList<>();
62     List<Integer> codes = new ArrayList<>();
63     List<String> respones = new ArrayList<>();
64
65     public void start() throws Exception {
66         configureServer();
67         startServer();
68     }
69
70     private void startServer() throws Exception {
71         requests.clear();
72         codes.clear();
73         _server.start();
74     }
75
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() {
86             @Override
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);
92             }
93         });
94     }
95
96     public void stop() throws Exception {
97         _server.stop();
98     }
99 }
100
101 public class TestCbamTokenProvider extends TestBase {
102
103     private static String GOOD_RESPONSE = "{ \"access_token\" : \"myToken\", \"expires_in\" : 1000 }";
104     @InjectMocks
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;
110
111     @Before
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();
123         testServer.start();
124         URI uri = testServer._server.getURI();
125         setField(cbamTokenProvider, "cbamKeyCloakBaseUrl", uri.toString());
126     }
127
128     private void addGoodTokenResponse() {
129         testServer.respones.add(GOOD_RESPONSE);
130         testServer.codes.add(HttpStatus.OK.value());
131     }
132
133     @After
134     public void testServer() throws Exception {
135         testServer.stop();
136     }
137
138     /**
139      * a new token is requested no token has been requested before
140      */
141     @Test
142     public void testBasicTokenRequest() throws Exception {
143         //given
144         addGoodTokenResponse();
145         //when
146         String token = cbamTokenProvider.getToken(VNFM_ID);
147         //verify
148         assertEquals(1, testServer.requests.size());
149         assertTokenRequest(testServer.requests.get(0));
150         assertEquals("myToken", token);
151
152     }
153
154     /**
155      * a new token is requested if the previous token has expired
156      */
157     @Test
158     public void testTokenIsRequestedIfPreviousExpired() throws Exception {
159         //given
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);
165         //when
166         String token = cbamTokenProvider.getToken(VNFM_ID);
167         //verify
168         assertEquals(2, testServer.requests.size());
169         assertTokenRequest(testServer.requests.get(0));
170         assertTokenRequest(testServer.requests.get(1));
171         assertEquals("myToken2", token);
172     }
173
174     /**
175      * a new token is not requested if the previous token has not expired
176      */
177     @Test
178     public void testTokenIsNotRequestedIfPreviousHasNotExpired() throws Exception {
179         //given
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);
185         //when
186         String token = cbamTokenProvider.getToken(VNFM_ID);
187         //verify
188         assertEquals(1, testServer.requests.size());
189         assertTokenRequest(testServer.requests.get(0));
190         assertEquals("myToken", token);
191     }
192
193     /**
194      * failed token requests are retried for a fixed number amount of times
195      */
196     @Test
197     public void testRetry() throws Exception {
198         //given
199         addFailedResponse();
200         addFailedResponse();
201         addFailedResponse();
202         addFailedResponse();
203         addGoodTokenResponse();
204         //cbamTokenProvider.failOnRequestNumber = 5;
205         //when
206         String token = cbamTokenProvider.getToken(VNFM_ID);
207         //verify
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);
219     }
220
221     /**
222      * failed token requests are retried for a fixed number amount of times (reacing maximal number or retries)
223      */
224     @Test
225     public void testNoMoreRetry() throws Exception {
226         //given
227         addFailedResponse();
228         addFailedResponse();
229         addFailedResponse();
230         addFailedResponse();
231         addFailedResponse();
232         //when
233         try {
234             cbamTokenProvider.getToken(VNFM_ID);
235             fail();
236         } catch (RuntimeException e) {
237             assertNotNull(e.getCause());
238         }
239         //verify
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());
251     }
252
253     private void addFailedResponse() {
254         testServer.codes.add(HttpStatus.UNAUTHORIZED.value());
255         testServer.respones.add(new String());
256     }
257
258     /**
259      * the SSL connection is established without certificate & hostname verification
260      */
261     @Test
262     public void noSslVerification() throws Exception {
263         //given
264         //the default settings is no SSL & hostname check
265         addGoodTokenResponse();
266         //when
267         cbamTokenProvider.getToken(VNFM_ID);
268         //verify
269         //no exception is thrown
270     }
271
272     /**
273      * if SSL is verified the certificates must be defined
274      */
275     @Test
276     public void testInvalidCombinationOfSettings() throws Exception {
277         //given
278         setField(cbamTokenProvider, "skipCertificateVerification", false);
279         //when
280         try {
281             cbamTokenProvider.getToken(VNFM_ID);
282             //verify
283             fail();
284         } catch (RuntimeException e) {
285             assertEquals("If the skipCertificateVerification is set to false (default) the trustedCertificates can not be empty", e.getMessage());
286         }
287     }
288
289     /**
290      * if SSL is verified the certificates must be defined
291      */
292     @Test
293     public void testInvalidCombinationOfSettings2() throws Exception {
294         //given
295         setField(cbamTokenProvider, "skipCertificateVerification", false);
296         setField(cbamTokenProvider, "trustedCertificates", "xx\nxx");
297         //when
298         try {
299             cbamTokenProvider.getToken(VNFM_ID);
300             //verify
301             fail();
302         } catch (RuntimeException e) {
303             assertEquals("The trustedCertificates must be a base64 encoded collection of PEM certificates", e.getMessage());
304             assertNotNull(e.getCause());
305         }
306     }
307
308     /**
309      * the SSL connection is established without certificate & hostname verification
310      */
311     @Test
312     public void testNotTrustedSslConnection() throws Exception {
313         //given
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();
318         //when
319         try {
320             cbamTokenProvider.getToken(VNFM_ID);
321             //verify
322             fail();
323         } catch (RuntimeException e) {
324             assertTrue(e.getMessage().contains("unable to find valid certification path"));
325             assertTrue(e.getCause() instanceof SSLHandshakeException);
326         }
327     }
328
329     /**
330      * the SSL connection is established with certificate & hostname verification
331      */
332     @Test
333     public void testHostnameVerificationSucceeds() throws Exception {
334         //given
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();
341         //when
342         cbamTokenProvider.getToken(VNFM_ID);
343         //verify
344         //no seception is thrown
345     }
346
347     /**
348      * the SSL connection is dropped with certificate & hostname verification due to invalid hostname
349      */
350     @Test
351     public void testHostnameverifcationfail() throws Exception {
352         //given
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();
358         //when
359         try {
360             cbamTokenProvider.getToken(VNFM_ID);
361             //verify
362             fail();
363         } catch (RuntimeException e) {
364             assertTrue(e.getMessage().contains("Hostname 127.0.0.1 not verified"));
365             assertTrue(e.getCause() instanceof SSLPeerUnverifiedException);
366         }
367     }
368
369     /**
370      * invalid certificate content
371      */
372     @Test
373     public void testInvalidCerificateContent() throws Exception {
374         //given
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();
379         //when
380         try {
381             cbamTokenProvider.getToken(VNFM_ID);
382             //verify
383             fail();
384         } catch (RuntimeException e) {
385             assertTrue(e.getMessage().contains("Unable to load certificates"));
386             assertTrue(e.getCause() instanceof GeneralSecurityException);
387         }
388     }
389
390     /**
391      * Verify client certificates are not verified
392      * \
393      */
394     @Test
395     public void testClientCertificates() throws Exception {
396         //when
397         new CbamTokenProvider.AllTrustedTrustManager().checkClientTrusted(null, null);
398         //verify
399         //no security exception is thrown
400     }
401
402     /**
403      * Exception during keystore creation is logged (semi-useless)
404      */
405     @Test
406     public void testKeystoreCreationFailure() {
407         KeyStoreException expectedException = new KeyStoreException();
408         class X extends CbamTokenProvider {
409             X(VnfmInfoProvider vnfmInfoProvider) {
410                 super(vnfmInfoProvider);
411             }
412
413             @Override
414             TrustManager[] buildTrustManager() throws KeyStoreException, NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException, CertificateException {
415                 throw expectedException;
416             }
417         }
418         try {
419             new X(null).buildSSLSocketFactory();
420             fail();
421         } catch (RuntimeException e) {
422             assertEquals(expectedException, e.getCause());
423             verify(logger).error("Unable to create SSL socket factory", expectedException);
424         }
425     }
426
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");
433     }
434
435     private void assertContains(String content, String key, String value) {
436         assertTrue(content.contains(key + "=" + value));
437     }
438 }