d31ff6ea3c1205b21c0fec1005fe4f3fdda20bd8
[nonrtric/plt/sme.git] / capifcore / internal / securityservice / security_test.go
1 // -
2 //   ========================LICENSE_START=================================
3 //   O-RAN-SC
4 //   %%
5 //   Copyright (C) 2022: Nordix Foundation
6 //   %%
7 //   Licensed under the Apache License, Version 2.0 (the "License");
8 //   you may not use this file except in compliance with the License.
9 //   You may obtain a copy of the License at
10 //
11 //        http://www.apache.org/licenses/LICENSE-2.0
12 //
13 //   Unless required by applicable law or agreed to in writing, software
14 //   distributed under the License is distributed on an "AS IS" BASIS,
15 //   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 //   See the License for the specific language governing permissions and
17 //   limitations under the License.
18 //   ========================LICENSE_END===================================
19 //
20
21 package security
22
23 import (
24         "errors"
25         "fmt"
26         "net/http"
27         "net/url"
28         "os"
29         "testing"
30
31         "oransc.org/nonrtric/capifcore/internal/common29122"
32         "oransc.org/nonrtric/capifcore/internal/keycloak"
33         "oransc.org/nonrtric/capifcore/internal/publishserviceapi"
34         "oransc.org/nonrtric/capifcore/internal/securityapi"
35
36         "oransc.org/nonrtric/capifcore/internal/invokermanagement"
37         "oransc.org/nonrtric/capifcore/internal/providermanagement"
38         "oransc.org/nonrtric/capifcore/internal/publishservice"
39
40         "github.com/labstack/echo/v4"
41
42         invokermocks "oransc.org/nonrtric/capifcore/internal/invokermanagement/mocks"
43         keycloackmocks "oransc.org/nonrtric/capifcore/internal/keycloak/mocks"
44         servicemocks "oransc.org/nonrtric/capifcore/internal/providermanagement/mocks"
45         publishmocks "oransc.org/nonrtric/capifcore/internal/publishservice/mocks"
46
47         "github.com/deepmap/oapi-codegen/pkg/middleware"
48         "github.com/deepmap/oapi-codegen/pkg/testutil"
49         echomiddleware "github.com/labstack/echo/v4/middleware"
50         "github.com/stretchr/testify/assert"
51         "github.com/stretchr/testify/mock"
52 )
53
54 func TestPostSecurityIdTokenInvokerRegistered(t *testing.T) {
55         invokerRegisterMock := invokermocks.InvokerRegister{}
56         invokerRegisterMock.On("IsInvokerRegistered", mock.AnythingOfType("string")).Return(true)
57         invokerRegisterMock.On("VerifyInvokerSecret", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(true)
58         serviceRegisterMock := servicemocks.ServiceRegister{}
59         serviceRegisterMock.On("IsFunctionRegistered", mock.AnythingOfType("string")).Return(true)
60         publishRegisterMock := publishmocks.PublishRegister{}
61         publishRegisterMock.On("IsAPIPublished", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(true)
62
63         jwt := keycloak.Jwttoken{
64                 AccessToken: "eyJhbGNIn0.e3YTQ0xLjEifQ.FcqCwCy7iJiOmw",
65                 ExpiresIn:   300,
66                 Scope:       "3gpp#aefIdpath",
67         }
68         accessMgmMock := keycloackmocks.AccessManagement{}
69         accessMgmMock.On("GetToken", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(jwt, nil)
70
71         requestHandler := getEcho(&serviceRegisterMock, &publishRegisterMock, &invokerRegisterMock, &accessMgmMock)
72
73         data := url.Values{}
74         clientId := "id"
75         clientSecret := "secret"
76         aefId := "aefId"
77         path := "path"
78         data.Set("client_id", clientId)
79         data.Set("client_secret", clientSecret)
80         data.Set("grant_type", "client_credentials")
81         data.Set("scope", "3gpp#"+aefId+":"+path)
82
83         encodedData := data.Encode()
84
85         result := testutil.NewRequest().Post("/securities/invokerId/token").WithContentType("application/x-www-form-urlencoded").WithBody([]byte(encodedData)).Go(t, requestHandler)
86
87         assert.Equal(t, http.StatusCreated, result.Code())
88         var resultResponse securityapi.AccessTokenRsp
89         err := result.UnmarshalBodyToObject(&resultResponse)
90         assert.NoError(t, err, "error unmarshaling response")
91         assert.NotEmpty(t, resultResponse.AccessToken)
92         assert.Equal(t, securityapi.AccessTokenRspTokenTypeBearer, resultResponse.TokenType)
93         invokerRegisterMock.AssertCalled(t, "IsInvokerRegistered", clientId)
94         invokerRegisterMock.AssertCalled(t, "VerifyInvokerSecret", clientId, clientSecret)
95         serviceRegisterMock.AssertCalled(t, "IsFunctionRegistered", aefId)
96         publishRegisterMock.AssertCalled(t, "IsAPIPublished", aefId, path)
97         accessMgmMock.AssertCalled(t, "GetToken", clientId, clientSecret, "3gpp#"+aefId+":"+path, "invokerrealm")
98 }
99
100 func TestPostSecurityIdTokenInvokerNotRegistered(t *testing.T) {
101         invokerRegisterMock := invokermocks.InvokerRegister{}
102         invokerRegisterMock.On("IsInvokerRegistered", mock.AnythingOfType("string")).Return(false)
103
104         requestHandler := getEcho(nil, nil, &invokerRegisterMock, nil)
105
106         data := url.Values{}
107         data.Set("client_id", "id")
108         data.Add("client_secret", "secret")
109         data.Add("grant_type", "client_credentials")
110         data.Add("scope", "3gpp#aefId:path")
111         encodedData := data.Encode()
112
113         result := testutil.NewRequest().Post("/securities/invokerId/token").WithContentType("application/x-www-form-urlencoded").WithBody([]byte(encodedData)).Go(t, requestHandler)
114
115         assert.Equal(t, http.StatusBadRequest, result.Code())
116         var errDetails securityapi.AccessTokenErr
117         err := result.UnmarshalBodyToObject(&errDetails)
118         assert.NoError(t, err, "error unmarshaling response")
119         assert.Equal(t, securityapi.AccessTokenErrErrorInvalidClient, errDetails.Error)
120         errMsg := "Invoker not registered"
121         assert.Equal(t, &errMsg, errDetails.ErrorDescription)
122 }
123
124 func TestPostSecurityIdTokenInvokerSecretNotValid(t *testing.T) {
125         invokerRegisterMock := invokermocks.InvokerRegister{}
126         invokerRegisterMock.On("IsInvokerRegistered", mock.AnythingOfType("string")).Return(true)
127         invokerRegisterMock.On("VerifyInvokerSecret", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(false)
128
129         requestHandler := getEcho(nil, nil, &invokerRegisterMock, nil)
130
131         data := url.Values{}
132         data.Set("client_id", "id")
133         data.Add("client_secret", "secret")
134         data.Add("grant_type", "client_credentials")
135         data.Add("scope", "3gpp#aefId:path")
136         encodedData := data.Encode()
137
138         result := testutil.NewRequest().Post("/securities/invokerId/token").WithContentType("application/x-www-form-urlencoded").WithBody([]byte(encodedData)).Go(t, requestHandler)
139
140         assert.Equal(t, http.StatusBadRequest, result.Code())
141         var errDetails securityapi.AccessTokenErr
142         err := result.UnmarshalBodyToObject(&errDetails)
143         assert.NoError(t, err, "error unmarshaling response")
144         assert.Equal(t, securityapi.AccessTokenErrErrorUnauthorizedClient, errDetails.Error)
145         errMsg := "Invoker secret not valid"
146         assert.Equal(t, &errMsg, errDetails.ErrorDescription)
147 }
148
149 func TestPostSecurityIdTokenFunctionNotRegistered(t *testing.T) {
150         invokerRegisterMock := invokermocks.InvokerRegister{}
151         invokerRegisterMock.On("IsInvokerRegistered", mock.AnythingOfType("string")).Return(true)
152         invokerRegisterMock.On("VerifyInvokerSecret", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(true)
153         serviceRegisterMock := servicemocks.ServiceRegister{}
154         serviceRegisterMock.On("IsFunctionRegistered", mock.AnythingOfType("string")).Return(false)
155
156         requestHandler := getEcho(&serviceRegisterMock, nil, &invokerRegisterMock, nil)
157
158         data := url.Values{}
159         data.Set("client_id", "id")
160         data.Add("client_secret", "secret")
161         data.Add("grant_type", "client_credentials")
162         data.Add("scope", "3gpp#aefId:path")
163         encodedData := data.Encode()
164
165         result := testutil.NewRequest().Post("/securities/invokerId/token").WithContentType("application/x-www-form-urlencoded").WithBody([]byte(encodedData)).Go(t, requestHandler)
166
167         assert.Equal(t, http.StatusBadRequest, result.Code())
168         var errDetails securityapi.AccessTokenErr
169         err := result.UnmarshalBodyToObject(&errDetails)
170         assert.NoError(t, err, "error unmarshaling response")
171         assert.Equal(t, securityapi.AccessTokenErrErrorInvalidScope, errDetails.Error)
172         errMsg := "AEF Function not registered"
173         assert.Equal(t, &errMsg, errDetails.ErrorDescription)
174 }
175
176 func TestPostSecurityIdTokenAPINotPublished(t *testing.T) {
177         invokerRegisterMock := invokermocks.InvokerRegister{}
178         invokerRegisterMock.On("IsInvokerRegistered", mock.AnythingOfType("string")).Return(true)
179         invokerRegisterMock.On("VerifyInvokerSecret", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(true)
180         serviceRegisterMock := servicemocks.ServiceRegister{}
181         serviceRegisterMock.On("IsFunctionRegistered", mock.AnythingOfType("string")).Return(true)
182         publishRegisterMock := publishmocks.PublishRegister{}
183         publishRegisterMock.On("IsAPIPublished", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(false)
184
185         requestHandler := getEcho(&serviceRegisterMock, &publishRegisterMock, &invokerRegisterMock, nil)
186
187         data := url.Values{}
188         data.Set("client_id", "id")
189         data.Add("client_secret", "secret")
190         data.Add("grant_type", "client_credentials")
191         data.Add("scope", "3gpp#aefId:path")
192         encodedData := data.Encode()
193
194         result := testutil.NewRequest().Post("/securities/invokerId/token").WithContentType("application/x-www-form-urlencoded").WithBody([]byte(encodedData)).Go(t, requestHandler)
195
196         assert.Equal(t, http.StatusBadRequest, result.Code())
197         var errDetails securityapi.AccessTokenErr
198         err := result.UnmarshalBodyToObject(&errDetails)
199         assert.NoError(t, err, "error unmarshaling response")
200         assert.Equal(t, securityapi.AccessTokenErrErrorInvalidScope, errDetails.Error)
201         errMsg := "API not published"
202         assert.Equal(t, &errMsg, errDetails.ErrorDescription)
203 }
204
205 func TestPostSecurityIdTokenInvokerInvalidCredentials(t *testing.T) {
206         invokerRegisterMock := invokermocks.InvokerRegister{}
207         invokerRegisterMock.On("IsInvokerRegistered", mock.AnythingOfType("string")).Return(true)
208         invokerRegisterMock.On("VerifyInvokerSecret", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(true)
209         serviceRegisterMock := servicemocks.ServiceRegister{}
210         serviceRegisterMock.On("IsFunctionRegistered", mock.AnythingOfType("string")).Return(true)
211         publishRegisterMock := publishmocks.PublishRegister{}
212         publishRegisterMock.On("IsAPIPublished", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(true)
213
214         jwt := keycloak.Jwttoken{}
215         accessMgmMock := keycloackmocks.AccessManagement{}
216         accessMgmMock.On("GetToken", mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(jwt, errors.New("invalid_credentials"))
217
218         requestHandler := getEcho(&serviceRegisterMock, &publishRegisterMock, &invokerRegisterMock, &accessMgmMock)
219
220         data := url.Values{}
221         clientId := "id"
222         clientSecret := "secret"
223         aefId := "aefId"
224         path := "path"
225         data.Set("client_id", clientId)
226         data.Set("client_secret", clientSecret)
227         data.Set("grant_type", "client_credentials")
228         data.Set("scope", "3gpp#"+aefId+":"+path)
229
230         encodedData := data.Encode()
231
232         result := testutil.NewRequest().Post("/securities/invokerId/token").WithContentType("application/x-www-form-urlencoded").WithBody([]byte(encodedData)).Go(t, requestHandler)
233
234         assert.Equal(t, http.StatusBadRequest, result.Code())
235         var resultResponse securityapi.AccessTokenErr
236         err := result.UnmarshalBodyToObject(&resultResponse)
237         assert.NoError(t, err, "error unmarshaling response")
238         invokerRegisterMock.AssertCalled(t, "IsInvokerRegistered", clientId)
239         invokerRegisterMock.AssertCalled(t, "VerifyInvokerSecret", clientId, clientSecret)
240         serviceRegisterMock.AssertCalled(t, "IsFunctionRegistered", aefId)
241         publishRegisterMock.AssertCalled(t, "IsAPIPublished", aefId, path)
242         accessMgmMock.AssertCalled(t, "GetToken", clientId, clientSecret, "3gpp#"+aefId+":"+path, "invokerrealm")
243 }
244
245 func TestPutTrustedInvokerSuccessfully(t *testing.T) {
246         invokerRegisterMock := invokermocks.InvokerRegister{}
247         invokerRegisterMock.On("IsInvokerRegistered", mock.AnythingOfType("string")).Return(true)
248         aefId := "aefId"
249         aefProfile := getAefProfile(aefId)
250         aefProfile.SecurityMethods = &[]publishserviceapi.SecurityMethod{
251                 publishserviceapi.SecurityMethodPKI,
252         }
253         aefProfiles := []publishserviceapi.AefProfile{
254                 aefProfile,
255         }
256         apiId := "apiId"
257         publishedServices := []publishserviceapi.ServiceAPIDescription{
258                 {
259                         ApiId:       &apiId,
260                         AefProfiles: &aefProfiles,
261                 },
262         }
263         publishRegisterMock := publishmocks.PublishRegister{}
264         publishRegisterMock.On("GetAllPublishedServices").Return(publishedServices)
265
266         requestHandler := getEcho(nil, &publishRegisterMock, &invokerRegisterMock, nil)
267
268         invokerId := "invokerId"
269         serviceSecurityUnderTest := getServiceSecurity(aefId, apiId)
270         serviceSecurityUnderTest.SecurityInfo[0].ApiId = &apiId
271
272         result := testutil.NewRequest().Put("/trustedInvokers/"+invokerId).WithJsonBody(serviceSecurityUnderTest).Go(t, requestHandler)
273
274         assert.Equal(t, http.StatusCreated, result.Code())
275         var resultResponse securityapi.ServiceSecurity
276         err := result.UnmarshalBodyToObject(&resultResponse)
277         assert.NoError(t, err, "error unmarshaling response")
278         assert.NotEmpty(t, resultResponse.NotificationDestination)
279
280         for _, security := range resultResponse.SecurityInfo {
281                 assert.Equal(t, *security.ApiId, apiId)
282                 assert.Equal(t, *security.SelSecurityMethod, publishserviceapi.SecurityMethodPKI)
283         }
284         invokerRegisterMock.AssertCalled(t, "IsInvokerRegistered", invokerId)
285
286 }
287
288 func TestPutTrustedInkoverNotRegistered(t *testing.T) {
289         invokerRegisterMock := invokermocks.InvokerRegister{}
290         invokerRegisterMock.On("IsInvokerRegistered", mock.AnythingOfType("string")).Return(false)
291
292         requestHandler := getEcho(nil, nil, &invokerRegisterMock, nil)
293
294         invokerId := "invokerId"
295         serviceSecurityUnderTest := getServiceSecurity("aefId", "apiId")
296
297         result := testutil.NewRequest().Put("/trustedInvokers/"+invokerId).WithJsonBody(serviceSecurityUnderTest).Go(t, requestHandler)
298
299         badRequest := http.StatusBadRequest
300         assert.Equal(t, badRequest, result.Code())
301         var problemDetails common29122.ProblemDetails
302         err := result.UnmarshalBodyToObject(&problemDetails)
303         assert.NoError(t, err, "error unmarshaling response")
304         assert.Equal(t, &badRequest, problemDetails.Status)
305         assert.Contains(t, *problemDetails.Cause, "Invoker not registered")
306         invokerRegisterMock.AssertCalled(t, "IsInvokerRegistered", invokerId)
307 }
308
309 func TestPutTrustedInkoverInvalidInputServiceSecurity(t *testing.T) {
310         invokerRegisterMock := invokermocks.InvokerRegister{}
311         invokerRegisterMock.On("IsInvokerRegistered", mock.AnythingOfType("string")).Return(true)
312
313         requestHandler := getEcho(nil, nil, &invokerRegisterMock, nil)
314
315         invokerId := "invokerId"
316         notificationUrl := "url"
317         serviceSecurityUnderTest := getServiceSecurity("aefId", "apiId")
318         serviceSecurityUnderTest.NotificationDestination = common29122.Uri(notificationUrl)
319
320         result := testutil.NewRequest().Put("/trustedInvokers/"+invokerId).WithJsonBody(serviceSecurityUnderTest).Go(t, requestHandler)
321
322         badRequest := http.StatusBadRequest
323         assert.Equal(t, badRequest, result.Code())
324         var problemDetails common29122.ProblemDetails
325         err := result.UnmarshalBodyToObject(&problemDetails)
326         assert.NoError(t, err, "error unmarshaling response")
327         assert.Equal(t, &badRequest, problemDetails.Status)
328         assert.Contains(t, *problemDetails.Cause, "ServiceSecurity has invalid notificationDestination")
329         invokerRegisterMock.AssertCalled(t, "IsInvokerRegistered", invokerId)
330 }
331
332 func TestPutTrustedInvokerInterfaceDetailsNotNil(t *testing.T) {
333         invokerRegisterMock := invokermocks.InvokerRegister{}
334         invokerRegisterMock.On("IsInvokerRegistered", mock.AnythingOfType("string")).Return(true)
335         aefId := "aefId"
336         aefProfile := getAefProfile(aefId)
337         aefProfile.SecurityMethods = &[]publishserviceapi.SecurityMethod{
338                 publishserviceapi.SecurityMethodPKI,
339         }
340         aefProfiles := []publishserviceapi.AefProfile{
341                 aefProfile,
342         }
343         apiId := "apiId"
344         publishedServices := []publishserviceapi.ServiceAPIDescription{
345                 {
346                         ApiId:       &apiId,
347                         AefProfiles: &aefProfiles,
348                 },
349         }
350         publishRegisterMock := publishmocks.PublishRegister{}
351         publishRegisterMock.On("GetAllPublishedServices").Return(publishedServices)
352
353         requestHandler := getEcho(nil, &publishRegisterMock, &invokerRegisterMock, nil)
354
355         invokerId := "invokerId"
356         serviceSecurityUnderTest := getServiceSecurity(aefId, apiId)
357         serviceSecurityUnderTest.SecurityInfo[0] = securityapi.SecurityInformation{
358                 ApiId: &apiId,
359                 PrefSecurityMethods: []publishserviceapi.SecurityMethod{
360                         publishserviceapi.SecurityMethodOAUTH,
361                 },
362                 InterfaceDetails: &publishserviceapi.InterfaceDescription{
363                         SecurityMethods: &[]publishserviceapi.SecurityMethod{
364                                 publishserviceapi.SecurityMethodPSK,
365                         },
366                 },
367         }
368
369         result := testutil.NewRequest().Put("/trustedInvokers/"+invokerId).WithJsonBody(serviceSecurityUnderTest).Go(t, requestHandler)
370
371         assert.Equal(t, http.StatusCreated, result.Code())
372         var resultResponse securityapi.ServiceSecurity
373         err := result.UnmarshalBodyToObject(&resultResponse)
374         assert.NoError(t, err, "error unmarshaling response")
375         assert.NotEmpty(t, resultResponse.NotificationDestination)
376
377         for _, security := range resultResponse.SecurityInfo {
378                 assert.Equal(t, apiId, *security.ApiId)
379                 assert.Equal(t, publishserviceapi.SecurityMethodPSK, *security.SelSecurityMethod)
380         }
381         invokerRegisterMock.AssertCalled(t, "IsInvokerRegistered", invokerId)
382
383 }
384
385 func TestPutTrustedInvokerNotFoundSecurityMethod(t *testing.T) {
386         invokerRegisterMock := invokermocks.InvokerRegister{}
387         invokerRegisterMock.On("IsInvokerRegistered", mock.AnythingOfType("string")).Return(true)
388
389         aefProfiles := []publishserviceapi.AefProfile{
390                 getAefProfile("aefId"),
391         }
392         apiId := "apiId"
393         publishedServices := []publishserviceapi.ServiceAPIDescription{
394                 {
395                         ApiId:       &apiId,
396                         AefProfiles: &aefProfiles,
397                 },
398         }
399         publishRegisterMock := publishmocks.PublishRegister{}
400         publishRegisterMock.On("GetAllPublishedServices").Return(publishedServices)
401
402         requestHandler := getEcho(nil, &publishRegisterMock, &invokerRegisterMock, nil)
403
404         invokerId := "invokerId"
405         serviceSecurityUnderTest := getServiceSecurity("aefId", "apiId")
406
407         result := testutil.NewRequest().Put("/trustedInvokers/"+invokerId).WithJsonBody(serviceSecurityUnderTest).Go(t, requestHandler)
408
409         badRequest := http.StatusBadRequest
410         assert.Equal(t, badRequest, result.Code())
411         var problemDetails common29122.ProblemDetails
412         err := result.UnmarshalBodyToObject(&problemDetails)
413         assert.NoError(t, err, "error unmarshaling response")
414         assert.Equal(t, &badRequest, problemDetails.Status)
415         assert.Contains(t, *problemDetails.Cause, "not found")
416         assert.Contains(t, *problemDetails.Cause, "security method")
417         invokerRegisterMock.AssertCalled(t, "IsInvokerRegistered", invokerId)
418 }
419
420 func getEcho(serviceRegister providermanagement.ServiceRegister, publishRegister publishservice.PublishRegister, invokerRegister invokermanagement.InvokerRegister, keycloakMgm keycloak.AccessManagement) *echo.Echo {
421         swagger, err := securityapi.GetSwagger()
422         if err != nil {
423                 fmt.Fprintf(os.Stderr, "Error loading swagger spec\n: %s", err)
424                 os.Exit(1)
425         }
426
427         swagger.Servers = nil
428
429         s := NewSecurity(serviceRegister, publishRegister, invokerRegister, keycloakMgm)
430
431         e := echo.New()
432         e.Use(echomiddleware.Logger())
433         e.Use(middleware.OapiRequestValidator(swagger))
434
435         securityapi.RegisterHandlers(e, s)
436         return e
437 }
438
439 func getServiceSecurity(aefId string, apiId string) securityapi.ServiceSecurity {
440         return securityapi.ServiceSecurity{
441                 NotificationDestination: common29122.Uri("http://golang.cafe/"),
442                 SecurityInfo: []securityapi.SecurityInformation{
443                         {
444                                 AefId: &aefId,
445                                 ApiId: &apiId,
446                                 PrefSecurityMethods: []publishserviceapi.SecurityMethod{
447                                         publishserviceapi.SecurityMethodOAUTH,
448                                 },
449                         },
450                 },
451         }
452 }
453
454 func getAefProfile(aefId string) publishserviceapi.AefProfile {
455         return publishserviceapi.AefProfile{
456                 AefId: aefId,
457                 Versions: []publishserviceapi.Version{
458                         {
459                                 Resources: &[]publishserviceapi.Resource{
460                                         {
461                                                 CommType: "REQUEST_RESPONSE",
462                                         },
463                                 },
464                         },
465                 },
466         }
467 }