Merge "sonar fix:blocker issues in MsbMgmrImpl"
[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.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;
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     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 {
66         configureServer();
67         startServer();
68     }
69
70     private void startServer() throws Exception {
71         requests.clear();
72         codes.clear();
73         _server.start();
74         Future<?> serverStarted = executorService.submit(() -> {
75             while(true){
76                 try {
77                     Thread.sleep(10);
78                     if(_server.isStarted()){
79                         return;
80                     }
81                 } catch (InterruptedException e) {
82                 }
83             }
84         });
85         serverStarted.get(30, TimeUnit.SECONDS);
86     }
87
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() {
98             @Override
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);
104             }
105         });
106     }
107
108     public void stop() throws Exception {
109         _server.stop();
110     }
111 }
112
113 public class TestCbamTokenProvider extends TestBase {
114
115     private static String GOOD_RESPONSE = "{ \"access_token\" : \"myToken\", \"expires_in\" : 1000 }";
116     @InjectMocks
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;
122
123     @Before
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();
135         testServer.start();
136         URI uri = testServer._server.getURI();
137         setField(cbamTokenProvider, "cbamKeyCloakBaseUrl", uri.toString());
138
139
140     }
141
142     private void addGoodTokenResponse() {
143         testServer.respones.add(GOOD_RESPONSE);
144         testServer.codes.add(HttpStatus.OK.value());
145     }
146
147     @After
148     public void testServer() throws Exception {
149         testServer.stop();
150     }
151
152     /**
153      * a new token is requested no token has been requested before
154      */
155     @Test
156     public void testBasicTokenRequest() throws Exception {
157         //given
158         addGoodTokenResponse();
159         //when
160         String token = cbamTokenProvider.getToken(VNFM_ID);
161         //verify
162         assertEquals(1, testServer.requests.size());
163         assertTokenRequest(testServer.requests.get(0));
164         assertEquals("myToken", token);
165
166     }
167
168     /**
169      * a new token is requested if the previous token has expired
170      */
171     @Test
172     public void testTokenIsRequestedIfPreviousExpired() throws Exception {
173         //given
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);
179         //when
180         String token = cbamTokenProvider.getToken(VNFM_ID);
181         //verify
182         assertEquals(2, testServer.requests.size());
183         assertTokenRequest(testServer.requests.get(0));
184         assertTokenRequest(testServer.requests.get(1));
185         assertEquals("myToken2", token);
186     }
187
188     /**
189      * a new token is not requested if the previous token has not expired
190      */
191     @Test
192     public void testTokenIsNotRequestedIfPreviousHasNotExpired() throws Exception {
193         //given
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);
199         //when
200         String token = cbamTokenProvider.getToken(VNFM_ID);
201         //verify
202         assertEquals(1, testServer.requests.size());
203         assertTokenRequest(testServer.requests.get(0));
204         assertEquals("myToken", token);
205     }
206
207     /**
208      * failed token requests are retried for a fixed number amount of times
209      */
210     @Test
211     public void testRetry() throws Exception {
212         //given
213         addFailedResponse();
214         addFailedResponse();
215         addFailedResponse();
216         addFailedResponse();
217         addGoodTokenResponse();
218         //cbamTokenProvider.failOnRequestNumber = 5;
219         //when
220         String token = cbamTokenProvider.getToken(VNFM_ID);
221         //verify
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);
233     }
234
235     /**
236      * failed token requests are retried for a fixed number amount of times (reacing maximal number or retries)
237      */
238     @Test
239     public void testNoMoreRetry() throws Exception {
240         //given
241         addFailedResponse();
242         addFailedResponse();
243         addFailedResponse();
244         addFailedResponse();
245         addFailedResponse();
246         //when
247         try {
248             cbamTokenProvider.getToken(VNFM_ID);
249             fail();
250         } catch (RuntimeException e) {
251             assertNotNull(e.getCause());
252         }
253         //verify
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());
265     }
266
267     private void addFailedResponse() {
268         testServer.codes.add(HttpStatus.UNAUTHORIZED.value());
269         testServer.respones.add(new String());
270     }
271
272     /**
273      * the SSL connection is established without certificate & hostname verification
274      */
275     @Test
276     public void noSslVerification() throws Exception {
277         //given
278         //the default settings is no SSL & hostname check
279         addGoodTokenResponse();
280         //when
281         cbamTokenProvider.getToken(VNFM_ID);
282         //verify
283         //no exception is thrown
284     }
285
286     /**
287      * if SSL is verified the certificates must be defined
288      */
289     @Test
290     public void testInvalidCombinationOfSettings() throws Exception {
291         //given
292         setField(cbamTokenProvider, "skipCertificateVerification", false);
293         //when
294         try {
295             cbamTokenProvider.getToken(VNFM_ID);
296             //verify
297             fail();
298         } catch (RuntimeException e) {
299             assertEquals("If the skipCertificateVerification is set to false (default) the trustedCertificates can not be empty", e.getMessage());
300         }
301     }
302
303     /**
304      * if SSL is verified the certificates must be defined
305      */
306     @Test
307     public void testInvalidCombinationOfSettings2() throws Exception {
308         //given
309         setField(cbamTokenProvider, "skipCertificateVerification", false);
310         setField(cbamTokenProvider, "trustedCertificates", "xx\nxx");
311         //when
312         try {
313             cbamTokenProvider.getToken(VNFM_ID);
314             //verify
315             fail();
316         } catch (RuntimeException e) {
317             assertEquals("The trustedCertificates must be a base64 encoded collection of PEM certificates", e.getMessage());
318             assertNotNull(e.getCause());
319         }
320     }
321
322     /**
323      * the SSL connection is established without certificate & hostname verification
324      */
325     @Test
326     public void testNotTrustedSslConnection() throws Exception {
327         //given
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();
332         //when
333         try {
334             cbamTokenProvider.getToken(VNFM_ID);
335             //verify
336             fail();
337         } catch (RuntimeException e) {
338             assertTrue(e.getCause().getCause().getMessage().contains("unable to find valid certification path"));
339             assertTrue(e.getCause() instanceof SSLHandshakeException);
340         }
341     }
342
343     /**
344      * the SSL connection is established with certificate & hostname verification
345      */
346     @Test
347     public void testHostnameVerificationSucceeds() throws Exception {
348         //given
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();
355         //when
356         cbamTokenProvider.getToken(VNFM_ID);
357         //verify
358         //no seception is thrown
359     }
360
361     /**
362      * the SSL connection is dropped with certificate & hostname verification due to invalid hostname
363      */
364     @Test
365     public void testHostnameverifcationfail() throws Exception {
366         //given
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();
372         //when
373         try {
374             cbamTokenProvider.getToken(VNFM_ID);
375             //verify
376             fail();
377         } catch (RuntimeException e) {
378             assertTrue(e.getCause().getMessage().contains("Hostname 127.0.0.1 not verified"));
379             assertTrue(e.getCause() instanceof SSLPeerUnverifiedException);
380         }
381     }
382
383     /**
384      * invalid certificate content
385      */
386     @Test
387     public void testInvalidCerificateContent() throws Exception {
388         //given
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();
393         //when
394         try {
395             cbamTokenProvider.getToken(VNFM_ID);
396             //verify
397             fail();
398         } catch (RuntimeException e) {
399             assertEquals("Unable to load certificates", e.getMessage());
400             assertTrue(e.getCause() instanceof GeneralSecurityException);
401         }
402     }
403
404     /**
405      * Verify client certificates are not verified
406      * \
407      */
408     @Test
409     public void testClientCertificates() throws Exception {
410         //when
411         new CbamTokenProvider.AllTrustedTrustManager().checkClientTrusted(null, null);
412         //verify
413         //no security exception is thrown
414     }
415
416     /**
417      * Exception during keystore creation is logged (semi-useless)
418      */
419     @Test
420     public void testKeystoreCreationFailure() {
421         KeyStoreException expectedException = new KeyStoreException();
422         class X extends CbamTokenProvider {
423             X(VnfmInfoProvider vnfmInfoProvider) {
424                 super(vnfmInfoProvider);
425             }
426
427             @Override
428             TrustManager[] buildTrustManager() throws KeyStoreException {
429                 throw expectedException;
430             }
431         }
432         try {
433             new X(null).buildSSLSocketFactory();
434             fail();
435         } catch (RuntimeException e) {
436             assertEquals(expectedException, e.getCause());
437             verify(logger).error("Unable to create SSL socket factory", expectedException);
438         }
439     }
440
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");
447     }
448
449     private void assertContains(String content, String key, String value) {
450         assertTrue(content.contains(key + "=" + value));
451     }
452 }