Fix most sonar issues
[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
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;
54
55 class HttpTestServer {
56     Server _server;
57     List<String> requests = new ArrayList<>();
58     List<Integer> codes = new ArrayList<>();
59     List<String> respones = new ArrayList<>();
60
61     public void start() throws Exception {
62         configureServer();
63         startServer();
64     }
65
66     private void startServer() throws Exception {
67         requests.clear();
68         codes.clear();
69         _server.start();
70     }
71
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() {
82             @Override
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);
88             }
89         });
90     }
91
92     public void stop() throws Exception {
93         _server.stop();
94     }
95 }
96
97 public class TestCbamTokenProvider extends TestBase {
98
99     private static String GOOD_RESPONSE = "{ \"access_token\" : \"myToken\", \"expires_in\" : 1000 }";
100     @InjectMocks
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;
106
107     @Before
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();
119         testServer.start();
120         URI uri = testServer._server.getURI();
121         setField(cbamTokenProvider, "cbamKeyCloakBaseUrl", uri.toString());
122     }
123
124     private void addGoodTokenResponse() {
125         testServer.respones.add(GOOD_RESPONSE);
126         testServer.codes.add(HttpStatus.OK.value());
127     }
128
129     @After
130     public void testServer() throws Exception {
131         testServer.stop();
132     }
133
134     /**
135      * a new token is requested no token has been requested before
136      */
137     @Test
138     public void testBasicTokenRequest() throws Exception {
139         //given
140         addGoodTokenResponse();
141         //when
142         String token = cbamTokenProvider.getToken(VNFM_ID);
143         //verify
144         assertEquals(1, testServer.requests.size());
145         assertTokenRequest(testServer.requests.get(0));
146         assertEquals("myToken", token);
147
148     }
149
150     /**
151      * a new token is requested if the previous token has expired
152      */
153     @Test
154     public void testTokenIsRequestedIfPreviousExpired() throws Exception {
155         //given
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);
161         //when
162         String token = cbamTokenProvider.getToken(VNFM_ID);
163         //verify
164         assertEquals(2, testServer.requests.size());
165         assertTokenRequest(testServer.requests.get(0));
166         assertTokenRequest(testServer.requests.get(1));
167         assertEquals("myToken2", token);
168     }
169
170     /**
171      * a new token is not requested if the previous token has not expired
172      */
173     @Test
174     public void testTokenIsNotRequestedIfPreviousHasNotExpired() throws Exception {
175         //given
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);
181         //when
182         String token = cbamTokenProvider.getToken(VNFM_ID);
183         //verify
184         assertEquals(1, testServer.requests.size());
185         assertTokenRequest(testServer.requests.get(0));
186         assertEquals("myToken", token);
187     }
188
189     /**
190      * failed token requests are retried for a fixed number amount of times
191      */
192     @Test
193     public void testRetry() throws Exception {
194         //given
195         addFailedResponse();
196         addFailedResponse();
197         addFailedResponse();
198         addFailedResponse();
199         addGoodTokenResponse();
200         //cbamTokenProvider.failOnRequestNumber = 5;
201         //when
202         String token = cbamTokenProvider.getToken(VNFM_ID);
203         //verify
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);
215     }
216
217     /**
218      * failed token requests are retried for a fixed number amount of times (reacing maximal number or retries)
219      */
220     @Test
221     public void testNoMoreRetry() throws Exception {
222         //given
223         addFailedResponse();
224         addFailedResponse();
225         addFailedResponse();
226         addFailedResponse();
227         addFailedResponse();
228         //when
229         try {
230             cbamTokenProvider.getToken(VNFM_ID);
231             fail();
232         } catch (RuntimeException e) {
233             assertNotNull(e.getCause());
234         }
235         //verify
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());
247     }
248
249     private void addFailedResponse() {
250         testServer.codes.add(HttpStatus.UNAUTHORIZED.value());
251         testServer.respones.add(new String());
252     }
253
254     /**
255      * the SSL connection is established without certificate & hostname verification
256      */
257     @Test
258     public void noSslVerification() throws Exception {
259         //given
260         //the default settings is no SSL & hostname check
261         addGoodTokenResponse();
262         //when
263         cbamTokenProvider.getToken(VNFM_ID);
264         //verify
265         //no exception is thrown
266     }
267
268     /**
269      * if SSL is verified the certificates must be defined
270      */
271     @Test
272     public void testInvalidCombinationOfSettings() throws Exception {
273         //given
274         setField(cbamTokenProvider, "skipCertificateVerification", false);
275         //when
276         try {
277             cbamTokenProvider.getToken(VNFM_ID);
278             //verify
279             fail();
280         } catch (RuntimeException e) {
281             assertEquals("If the skipCertificateVerification is set to false (default) the trustedCertificates can not be empty", e.getMessage());
282         }
283     }
284
285     /**
286      * if SSL is verified the certificates must be defined
287      */
288     @Test
289     public void testInvalidCombinationOfSettings2() throws Exception {
290         //given
291         setField(cbamTokenProvider, "skipCertificateVerification", false);
292         setField(cbamTokenProvider, "trustedCertificates", "xx\nxx");
293         //when
294         try {
295             cbamTokenProvider.getToken(VNFM_ID);
296             //verify
297             fail();
298         } catch (RuntimeException e) {
299             assertEquals("The trustedCertificates must be a base64 encoded collection of PEM certificates", e.getMessage());
300             assertNotNull(e.getCause());
301         }
302     }
303
304     /**
305      * the SSL connection is established without certificate & hostname verification
306      */
307     @Test
308     public void testNotTrustedSslConnection() throws Exception {
309         //given
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();
314         //when
315         try {
316             cbamTokenProvider.getToken(VNFM_ID);
317             //verify
318             fail();
319         } catch (RuntimeException e) {
320             assertTrue(e.getCause().getCause().getMessage().contains("unable to find valid certification path"));
321             assertTrue(e.getCause() instanceof SSLHandshakeException);
322         }
323     }
324
325     /**
326      * the SSL connection is established with certificate & hostname verification
327      */
328     @Test
329     public void testHostnameVerificationSucceeds() throws Exception {
330         //given
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();
337         //when
338         cbamTokenProvider.getToken(VNFM_ID);
339         //verify
340         //no seception is thrown
341     }
342
343     /**
344      * the SSL connection is dropped with certificate & hostname verification due to invalid hostname
345      */
346     @Test
347     public void testHostnameverifcationfail() 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, "skipHostnameVerification", false);
353         addGoodTokenResponse();
354         //when
355         try {
356             cbamTokenProvider.getToken(VNFM_ID);
357             //verify
358             fail();
359         } catch (RuntimeException e) {
360             assertTrue(e.getCause().getMessage().contains("Hostname 127.0.0.1 not verified"));
361             assertTrue(e.getCause() instanceof SSLPeerUnverifiedException);
362         }
363     }
364
365     /**
366      * invalid certificate content
367      */
368     @Test
369     public void testInvalidCerificateContent() throws Exception {
370         //given
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();
375         //when
376         try {
377             cbamTokenProvider.getToken(VNFM_ID);
378             //verify
379             fail();
380         } catch (RuntimeException e) {
381             assertEquals("Unable to load certificates", e.getMessage());
382             assertTrue(e.getCause() instanceof GeneralSecurityException);
383         }
384     }
385
386     /**
387      * Verify client certificates are not verified
388      * \
389      */
390     @Test
391     public void testClientCertificates() throws Exception {
392         //when
393         new CbamTokenProvider.AllTrustedTrustManager().checkClientTrusted(null, null);
394         //verify
395         //no security exception is thrown
396     }
397
398     /**
399      * Exception during keystore creation is logged (semi-useless)
400      */
401     @Test
402     public void testKeystoreCreationFailure() {
403         KeyStoreException expectedException = new KeyStoreException();
404         class X extends CbamTokenProvider {
405             X(VnfmInfoProvider vnfmInfoProvider) {
406                 super(vnfmInfoProvider);
407             }
408
409             @Override
410             TrustManager[] buildTrustManager() throws KeyStoreException {
411                 throw expectedException;
412             }
413         }
414         try {
415             new X(null).buildSSLSocketFactory();
416             fail();
417         } catch (RuntimeException e) {
418             assertEquals(expectedException, e.getCause());
419             verify(logger).error("Unable to create SSL socket factory", expectedException);
420         }
421     }
422
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");
429     }
430
431     private void assertContains(String content, String key, String value) {
432         assertTrue(content.contains(key + "=" + value));
433     }
434 }