e6a66800b42ab905e4c98119b285c840e33e4551
[ccsdk/cds.git] /
1 /*
2  * Copyright © 2017-2018 AT&T Intellectual Property.
3  * Copyright (C) 2019 Nordix Foundation
4  * Modifications Copyright © 2019 Huawei.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18
19 package org.onap.ccsdk.cds.blueprintsprocessor.rest.service
20
21 import com.fasterxml.jackson.databind.JsonNode
22 import com.fasterxml.jackson.databind.ObjectMapper
23 import kotlinx.coroutines.CoroutineStart
24 import kotlinx.coroutines.async
25 import kotlinx.coroutines.runBlocking
26 import org.junit.After
27 import org.junit.Before
28 import org.junit.Test
29 import org.junit.runner.RunWith
30 import org.onap.ccsdk.cds.blueprintsprocessor.core.BluePrintProperties
31 import org.onap.ccsdk.cds.blueprintsprocessor.core.BlueprintPropertyConfiguration
32 import org.onap.ccsdk.cds.blueprintsprocessor.rest.BluePrintRestLibConfiguration
33 import org.springframework.beans.factory.annotation.Autowired
34 import org.springframework.boot.autoconfigure.EnableAutoConfiguration
35 import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
36 import org.springframework.boot.test.context.SpringBootTest
37 import org.springframework.boot.web.embedded.netty.NettyReactiveWebServerFactory
38 import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory
39 import org.springframework.boot.web.server.WebServer
40 import org.springframework.context.annotation.Bean
41 import org.springframework.http.HttpMethod
42 import org.springframework.http.server.reactive.HttpHandler
43 import org.springframework.security.config.web.server.ServerHttpSecurity
44 import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
45 import org.springframework.security.core.userdetails.User
46 import org.springframework.security.core.userdetails.UserDetails
47 import org.springframework.security.web.server.SecurityWebFilterChain
48 import org.springframework.test.context.ContextConfiguration
49 import org.springframework.test.context.TestPropertySource
50 import org.springframework.test.context.junit4.SpringRunner
51 import org.springframework.web.bind.annotation.DeleteMapping
52 import org.springframework.web.bind.annotation.GetMapping
53 import org.springframework.web.bind.annotation.PatchMapping
54 import org.springframework.web.bind.annotation.PostMapping
55 import org.springframework.web.bind.annotation.PutMapping
56 import org.springframework.web.bind.annotation.RequestHeader
57 import org.springframework.web.bind.annotation.RequestMapping
58 import org.springframework.web.bind.annotation.RestController
59 import kotlin.test.assertEquals
60 import kotlin.test.assertNotNull
61
62 @RunWith(SpringRunner::class)
63 @EnableAutoConfiguration(exclude = [DataSourceAutoConfiguration::class])
64 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
65 @ContextConfiguration(classes = [BluePrintRestLibConfiguration::class, SampleController::class,
66     SecurityConfiguration::class,
67     BlueprintPropertyConfiguration::class, BluePrintProperties::class])
68 @TestPropertySource(properties =
69 [
70     "server.port=8443",
71     "server.ssl.enabled=true",
72     "server.ssl.key-store=classpath:keystore.p12",
73     "server.ssl.key-store-password=changeit",
74     "server.ssl.keyStoreType=PKCS12",
75     "server.ssl.keyAlias=tomcat",
76     "blueprintsprocessor.restclient.sample.type=basic-auth",
77     "blueprintsprocessor.restclient.sample.url=http://127.0.0.1:8081",
78     "blueprintsprocessor.restclient.sample.username=admin",
79     "blueprintsprocessor.restclient.sample.password=jans",
80     "blueprintsprocessor.restclient.test.type=ssl-basic-auth",
81     "blueprintsprocessor.restclient.test.url=https://localhost:8443",
82     "blueprintsprocessor.restclient.test.username=admin",
83     "blueprintsprocessor.restclient.test.password=jans",
84     "blueprintsprocessor.restclient.test.keyStoreInstance=PKCS12",
85     "blueprintsprocessor.restclient.test.sslTrust=src/test/resources/keystore.p12",
86     "blueprintsprocessor.restclient.test.sslTrustPassword=changeit"
87 ])
88 class RestClientServiceTest {
89
90     @Autowired
91     lateinit var bluePrintRestLibPropertyService: BluePrintRestLibPropertyService
92
93     @Autowired
94     lateinit var httpHandler : HttpHandler
95
96     lateinit var http : WebServer
97
98     fun localPort() = http.port
99
100     @Before
101     fun start() {
102         // Second Http server required for non-SSL requests to be processed along with the https server.
103         val factory: ReactiveWebServerFactory = NettyReactiveWebServerFactory(8081)
104         this.http = factory.getWebServer(this.httpHandler)
105         this.http.start()
106     }
107
108     @After
109     fun stop() {
110         this.http.stop()
111     }
112
113     @Test
114     fun testPatch() {
115         val restClientService = bluePrintRestLibPropertyService
116                 .blueprintWebClientService("sample")
117         val response = restClientService.exchangeResource(
118                 HttpMethod.PATCH.name, "/sample/name", "")
119         assertEquals("Patch request successful", response.body,
120                 "failed to get patch response")
121     }
122
123     @Test
124     fun testBaseAuth() {
125         val restClientService = bluePrintRestLibPropertyService
126                 .blueprintWebClientService("sample")
127         val headers = mutableMapOf<String, String>()
128         headers["X-Transaction-Id"] = "1234"
129         val response = restClientService.exchangeResource(HttpMethod.GET.name,
130                 "/sample/name", "")
131         assertNotNull(response.body, "failed to get response")
132     }
133
134     @Test
135     fun testSimpleBasicAuth() {
136         val json: String = "{\n" +
137                 "  \"type\" : \"basic-auth\",\n" +
138                 "  \"url\" : \"http://localhost:8081\",\n" +
139                 "  \"username\" : \"admin\",\n" +
140                 "  \"password\" : \"jans\"\n" +
141                 "}"
142         val mapper = ObjectMapper()
143         val actualObj: JsonNode = mapper.readTree(json)
144         val restClientService = bluePrintRestLibPropertyService
145                 .blueprintWebClientService(actualObj)
146         lateinit var res:String
147         runBlocking {
148             val get = async(start = CoroutineStart.LAZY) {
149                 restClientService.exchangeNB(HttpMethod.GET.name,
150                         "/sample/basic", "").body}
151             get.start()
152             res = get.await()
153         }
154         assertNotNull(res, "failed to get response")
155         assertEquals(res, "Basic request arrived successfully")
156     }
157
158     @Test
159     fun testSampleAaiReq() {
160         val restClientService = bluePrintRestLibPropertyService
161                 .blueprintWebClientService("test")
162         val headers = mutableMapOf<String, String>()
163         headers["X-TransactionId"] = "9999"
164         headers["X-FromAppId"] = "AAI"
165         val post1 = "{\n" +
166                 "  \"customer\": {\n" +
167                 "    \"global-customer-id\": \"ONSDEMOBJHKCustomer\",\n" +
168                 "    \"subscriber-name\": \"ONSDEMOBJHKCustomer\",\n" +
169                 "    \"subscriber-type\": \"CUST\",\n" +
170                 "    \"resource-version\": \"1552985011163\"\n" +
171                 "  }\n" +
172                 "}"
173         lateinit var res1: Customer
174         lateinit var res2: Customer
175         lateinit var res3: String
176         lateinit var res4: String
177         lateinit var res5: String
178         lateinit var res6: String
179         runBlocking {
180             val get1 = async(start = CoroutineStart.LAZY) {
181                 restClientService.exchangeNB(HttpMethod.GET.name,
182                         "/sample/aai/v14/business/customers", "", headers,
183                         Customer::class.java).body}
184
185             val get2 = async(start = CoroutineStart.LAZY) {
186                 restClientService.exchangeNB(HttpMethod.GET.name,
187                         "/sample/aai/v14/business/customers", "", headers,
188                         Customer::class.java).body}
189
190             val post = async(start = CoroutineStart.LAZY) {
191                 restClientService.exchangeNB(HttpMethod.POST.name,
192                         "/sample/aai/v14/business/customers", post1, headers,
193                         String::class.java).body}
194
195             val put = async(start = CoroutineStart.LAZY) {
196                 restClientService.exchangeNB(HttpMethod.PUT.name,
197                         "/sample/aai/v14/business/customers", post1, headers,
198                         String::class.java).body}
199
200             val patch = async(start = CoroutineStart.LAZY) {
201                 restClientService.exchangeNB(HttpMethod.PATCH.name,
202                         "/sample/aai/v14/business/customers", post1, headers,
203                         String::class.java).body}
204
205             val delete = async(start = CoroutineStart.LAZY) {
206                 restClientService.exchangeNB(HttpMethod.DELETE.name,
207                         "/sample/aai/v14/business/customers", "", headers,
208                         String::class.java).body}
209
210             get1.start()
211             get2.start()
212             post.start()
213             put.start()
214             patch.start()
215             delete.start()
216             res1 = get1.await()
217             res2 = get2.await()
218             res3 = post.await()
219             res4 = put.await()
220             res5 = patch.await()
221             res6 = delete.await()
222         }
223         assertNotNull(res1, "failed to get response")
224         assertNotNull(res2, "failed to get response")
225         assertEquals(res1.id, "ONSDEMOBJHKCustomer")
226         assertEquals(res1.name, "ONSDEMOBJHKCustomer")
227         assertEquals(res1.type, "CUST")
228         assertEquals(res1.resource, "1552985011163")
229         assertEquals(res2.id, "ONSDEMOBJHKCustomer")
230         assertEquals(res2.name, "ONSDEMOBJHKCustomer")
231         assertEquals(res2.type, "CUST")
232         assertEquals(res2.resource, "1552985011163")
233         assertEquals(res3, "The message is successfully posted")
234         assertEquals(res4, "The put request is success")
235         assertEquals(res5, "The patch request is success")
236         assertEquals(res6, "The message is successfully deleted")
237     }
238 }
239
240 /**
241  * Sample controller code for testing both http and https requests.
242  */
243 @RestController
244 @RequestMapping("/sample")
245 open class SampleController {
246
247     @GetMapping("/name")
248     fun getName(): String = "Sample Controller"
249
250     @PatchMapping("/name")
251     fun patchName(): String = "Patch request successful"
252
253     @GetMapping("/basic")
254     fun getBasic(): String = "Basic request arrived successfully"
255
256
257     @GetMapping("/aai/v14/business/customers")
258     fun getAaiCustomers(
259             @RequestHeader(name = "X-TransactionId", required = true)
260             transId: String,
261             @RequestHeader(name = "X-FromAppId", required = true)
262             appId: String) : String {
263         if (transId != "9999" || appId != "AAI") {
264             return ""
265         }
266         return "{\n" +
267                 "  \"id\": \"ONSDEMOBJHKCustomer\",\n" +
268                 "  \"name\": \"ONSDEMOBJHKCustomer\",\n" +
269                 "  \"type\": \"CUST\",\n" +
270                 "  \"resource\": \"1552985011163\"\n" +
271                 "}"
272     }
273
274     @PostMapping("/aai/v14/business/customers")
275     fun postAaiCustomers(
276             @RequestHeader(name = "X-TransactionId", required = true)
277             transId: String,
278             @RequestHeader(name = "X-FromAppId", required = true)
279             appId: String) : String {
280         if (transId != "9999" || appId != "AAI") {
281             return ""
282         }
283         return "The message is successfully posted"
284     }
285
286
287     @PutMapping("/aai/v14/business/customers")
288     fun putAaiCustomers(
289             @RequestHeader(name = "X-TransactionId", required = true)
290             transId: String,
291             @RequestHeader(name = "X-FromAppId", required = true)
292             appId: String) : String {
293         if (transId != "9999" || appId != "AAI") {
294             return ""
295         }
296         return "The put request is success"
297     }
298
299     @PatchMapping("/aai/v14/business/customers")
300     fun patchAaiCustomers(
301             @RequestHeader(name = "X-TransactionId", required = true)
302             transId: String,
303             @RequestHeader(name = "X-FromAppId", required = true)
304             appId: String) : String {
305         if (transId != "9999" || appId != "AAI") {
306             return ""
307         }
308         return "The patch request is success"
309     }
310
311     @DeleteMapping("/aai/v14/business/customers")
312     fun deleteAaiCustomers(
313             @RequestHeader(name = "X-TransactionId", required = true)
314             transId: String,
315             @RequestHeader(name = "X-FromAppId", required = true)
316             appId: String) : String {
317         if (transId != "9999" || appId != "AAI") {
318             return ""
319         }
320         return "The message is successfully deleted"
321     }
322 }
323
324 /**
325  * Security configuration required for basic authentication with username and
326  * password for any request in the server.
327  */
328 open class SecurityConfiguration {
329
330     @Bean
331     open fun userDetailsService(): MapReactiveUserDetailsService {
332         val user: UserDetails = User.withDefaultPasswordEncoder()
333                 .username("admin")
334                 .password("jans")
335                 .roles("USER")
336                 .build()
337         return MapReactiveUserDetailsService(user)
338     }
339
340     @Bean
341     open fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
342         return http
343                 .csrf().disable()
344                 .authorizeExchange().anyExchange().authenticated()
345                 .and().httpBasic()
346                 .and().build()
347     }
348 }
349
350 /**
351  * Data class required for response
352  */
353 data class Customer(
354        val id: String,
355        val name: String,
356        val type: String,
357        val resource: String)