NONRTRIC-946: Fix Capifcore intersection panic
[nonrtric/plt/sme.git] / capifcore / internal / securityservice / security.go
1 // -
2 //   ========================LICENSE_START=================================
3 //   O-RAN-SC
4 //   %%
5 //   Copyright (C) 2022-2023: Nordix Foundation
6 //   Copyright (C) 2024: OpenInfra Foundation Europe
7 //   %%
8 //   Licensed under the Apache License, Version 2.0 (the "License");
9 //   you may not use this file except in compliance with the License.
10 //   You may obtain a copy of the License at
11 //
12 //        http://www.apache.org/licenses/LICENSE-2.0
13 //
14 //   Unless required by applicable law or agreed to in writing, software
15 //   distributed under the License is distributed on an "AS IS" BASIS,
16 //   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 //   See the License for the specific language governing permissions and
18 //   limitations under the License.
19 //   ========================LICENSE_END===================================
20 //
21
22 package security
23
24 import (
25         "fmt"
26         "net/http"
27         "net/url"
28         "path"
29         "strings"
30         "sync"
31
32         "github.com/labstack/echo/v4"
33         copystructure "github.com/mitchellh/copystructure"
34         "k8s.io/utils/strings/slices"
35         "oransc.org/nonrtric/capifcore/internal/common29122"
36         securityapi "oransc.org/nonrtric/capifcore/internal/securityapi"
37
38         "oransc.org/nonrtric/capifcore/internal/invokermanagement"
39         "oransc.org/nonrtric/capifcore/internal/keycloak"
40         "oransc.org/nonrtric/capifcore/internal/providermanagement"
41         "oransc.org/nonrtric/capifcore/internal/publishservice"
42 )
43
44 type Security struct {
45         serviceRegister providermanagement.ServiceRegister
46         publishRegister publishservice.PublishRegister
47         invokerRegister invokermanagement.InvokerRegister
48         keycloak        keycloak.AccessManagement
49         trustedInvokers map[string]securityapi.ServiceSecurity
50         lock            sync.Mutex
51 }
52
53 func NewSecurity(serviceRegister providermanagement.ServiceRegister, publishRegister publishservice.PublishRegister, invokerRegister invokermanagement.InvokerRegister, km keycloak.AccessManagement) *Security {
54         return &Security{
55                 serviceRegister: serviceRegister,
56                 publishRegister: publishRegister,
57                 invokerRegister: invokerRegister,
58                 keycloak:        km,
59                 trustedInvokers: make(map[string]securityapi.ServiceSecurity),
60         }
61 }
62
63 func (s *Security) PostSecuritiesSecurityIdToken(ctx echo.Context, securityId string) error {
64         var accessTokenReq securityapi.AccessTokenReq
65         accessTokenReq.GetAccessTokenReq(ctx)
66
67         if valid, err := accessTokenReq.Validate(); !valid {
68                 return ctx.JSON(http.StatusBadRequest, err)
69         }
70
71         if !s.invokerRegister.IsInvokerRegistered(accessTokenReq.ClientId) {
72                 return sendAccessTokenError(ctx, http.StatusBadRequest, securityapi.AccessTokenErrErrorInvalidClient, "Invoker not registered")
73         }
74
75         if !s.invokerRegister.VerifyInvokerSecret(accessTokenReq.ClientId, *accessTokenReq.ClientSecret) {
76                 return sendAccessTokenError(ctx, http.StatusBadRequest, securityapi.AccessTokenErrErrorUnauthorizedClient, "Invoker secret not valid")
77         }
78
79         if accessTokenReq.Scope != nil && *accessTokenReq.Scope != "" {
80                 scope := strings.Split(*accessTokenReq.Scope, "#")
81                 aefList := strings.Split(scope[1], ";")
82                 for _, aef := range aefList {
83                         apiList := strings.Split(aef, ":")
84                         if !s.serviceRegister.IsFunctionRegistered(apiList[0]) {
85                                 return sendAccessTokenError(ctx, http.StatusBadRequest, securityapi.AccessTokenErrErrorInvalidScope, "AEF Function not registered")
86                         }
87                         for _, api := range strings.Split(apiList[1], ",") {
88                                 if !s.publishRegister.IsAPIPublished(apiList[0], api) {
89                                         return sendAccessTokenError(ctx, http.StatusBadRequest, securityapi.AccessTokenErrErrorInvalidScope, "API not published")
90                                 }
91                         }
92                 }
93         }
94         data := url.Values{"grant_type": {"client_credentials"}, "client_id": {accessTokenReq.ClientId}, "client_secret": {*accessTokenReq.ClientSecret}}
95         jwtToken, err := s.keycloak.GetToken("invokerrealm", data)
96         if err != nil {
97                 return sendAccessTokenError(ctx, http.StatusBadRequest, securityapi.AccessTokenErrErrorUnauthorizedClient, err.Error())
98         }
99
100         accessTokenResp := securityapi.AccessTokenRsp{
101                 AccessToken: jwtToken.AccessToken,
102                 ExpiresIn:   common29122.DurationSec(jwtToken.ExpiresIn),
103                 Scope:       accessTokenReq.Scope,
104                 TokenType:   "Bearer",
105         }
106
107         err = ctx.JSON(http.StatusCreated, accessTokenResp)
108         if err != nil {
109                 // Something really bad happened, tell Echo that our handler failed
110                 return err
111         }
112
113         return nil
114 }
115
116 func (s *Security) DeleteTrustedInvokersApiInvokerId(ctx echo.Context, apiInvokerId string) error {
117         if _, ok := s.trustedInvokers[apiInvokerId]; ok {
118                 s.deleteTrustedInvoker(apiInvokerId)
119         }
120
121         return ctx.NoContent(http.StatusNoContent)
122 }
123
124 func (s *Security) deleteTrustedInvoker(apiInvokerId string) {
125         s.lock.Lock()
126         defer s.lock.Unlock()
127         delete(s.trustedInvokers, apiInvokerId)
128 }
129
130 func (s *Security) GetTrustedInvokersApiInvokerId(ctx echo.Context, apiInvokerId string, params securityapi.GetTrustedInvokersApiInvokerIdParams) error {
131
132         if trustedInvoker, ok := s.trustedInvokers[apiInvokerId]; ok {
133                 updatedInvoker := s.checkParams(trustedInvoker, params)
134                 if updatedInvoker != nil {
135                         err := ctx.JSON(http.StatusOK, updatedInvoker)
136                         if err != nil {
137                                 return err
138                         }
139                 }
140         } else {
141                 return sendCoreError(ctx, http.StatusNotFound, fmt.Sprintf("invoker %s not registered as trusted invoker", apiInvokerId))
142         }
143
144         return nil
145 }
146
147 func (s *Security) checkParams(trustedInvoker securityapi.ServiceSecurity, params securityapi.GetTrustedInvokersApiInvokerIdParams) *securityapi.ServiceSecurity {
148         emptyString := ""
149
150         var sendAuthenticationInfo = (params.AuthenticationInfo != nil) && *params.AuthenticationInfo
151         var sendAuthorizationInfo = (params.AuthorizationInfo != nil) && *params.AuthorizationInfo
152
153         if sendAuthenticationInfo && sendAuthorizationInfo {
154                 return &trustedInvoker
155         }
156
157         data, _ := copystructure.Copy(trustedInvoker)
158         updatedInvoker, ok := data.(securityapi.ServiceSecurity)
159         if !ok {
160                 return nil
161         }
162
163         if !sendAuthenticationInfo {
164                 for i := range updatedInvoker.SecurityInfo {
165                         updatedInvoker.SecurityInfo[i].AuthenticationInfo = &emptyString
166                 }
167         }
168         if !sendAuthorizationInfo {
169                 for i := range updatedInvoker.SecurityInfo {
170                         updatedInvoker.SecurityInfo[i].AuthorizationInfo = &emptyString
171                 }
172         }
173         return &updatedInvoker
174 }
175
176 func (s *Security) PutTrustedInvokersApiInvokerId(ctx echo.Context, apiInvokerId string) error {
177         errMsg := "Unable to update security context due to %s."
178
179         if !s.invokerRegister.IsInvokerRegistered(apiInvokerId) {
180                 return sendCoreError(ctx, http.StatusBadRequest, "Unable to update security context due to Invoker not registered")
181         }
182         serviceSecurity, err := getServiceSecurityFromRequest(ctx)
183         if err != nil {
184                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
185         }
186
187         if err := serviceSecurity.Validate(); err != nil {
188                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
189         }
190
191         err = s.prepareNewSecurityContext(&serviceSecurity, apiInvokerId)
192         if err != nil {
193                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
194         }
195
196         uri := ctx.Request().Host + ctx.Request().URL.String()
197         ctx.Response().Header().Set(echo.HeaderLocation, ctx.Scheme()+`://`+path.Join(uri, apiInvokerId))
198
199         err = ctx.JSON(http.StatusCreated, s.trustedInvokers[apiInvokerId])
200         if err != nil {
201                 // Something really bad happened, tell Echo that our handler failed
202                 return err
203         }
204
205         return nil
206 }
207
208 func getServiceSecurityFromRequest(ctx echo.Context) (securityapi.ServiceSecurity, error) {
209         var serviceSecurity securityapi.ServiceSecurity
210         err := ctx.Bind(&serviceSecurity)
211         if err != nil {
212                 return securityapi.ServiceSecurity{}, fmt.Errorf("invalid format for service security")
213         }
214         return serviceSecurity, nil
215 }
216
217 func (s *Security) prepareNewSecurityContext(newContext *securityapi.ServiceSecurity, apiInvokerId string) error {
218         s.lock.Lock()
219         defer s.lock.Unlock()
220
221         err := newContext.PrepareNewSecurityContext(s.publishRegister.GetAllPublishedServices())
222         if err != nil {
223                 return err
224         }
225
226         s.trustedInvokers[apiInvokerId] = *newContext
227         return nil
228 }
229
230 func (s *Security) PostTrustedInvokersApiInvokerIdDelete(ctx echo.Context, apiInvokerId string) error {
231         var notification securityapi.SecurityNotification
232
233         errMsg := "Unable to revoke invoker due to %s"
234
235         if err := ctx.Bind(&notification); err != nil {
236                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, "invalid format for security notification"))
237         }
238
239         if err := notification.Validate(); err != nil {
240                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
241         }
242
243         if ss, ok := s.trustedInvokers[apiInvokerId]; ok {
244                 securityInfoCopy := s.revokeTrustedInvoker(&ss, notification)
245
246                 if len(securityInfoCopy) == 0 {
247                         s.deleteTrustedInvoker(apiInvokerId)
248                 } else {
249                         ss.SecurityInfo = securityInfoCopy
250                         s.updateTrustedInvoker(ss, apiInvokerId)
251                 }
252
253         } else {
254                 return sendCoreError(ctx, http.StatusNotFound, "the invoker is not register as a trusted invoker")
255         }
256
257         return ctx.NoContent(http.StatusNoContent)
258
259 }
260
261 func (s *Security) revokeTrustedInvoker(ss *securityapi.ServiceSecurity, notification securityapi.SecurityNotification) []securityapi.SecurityInformation {
262
263         data, _ := copystructure.Copy(ss.SecurityInfo)
264         securityInfoCopy, _ := data.([]securityapi.SecurityInformation)
265
266         for i, context := range ss.SecurityInfo {
267                 if notification.AefId == context.AefId || slices.Contains(notification.ApiIds, *context.ApiId) {
268                         securityInfoCopy = append(securityInfoCopy[:i], securityInfoCopy[i+1:]...)
269                 }
270         }
271
272         return securityInfoCopy
273
274 }
275
276 func (s *Security) PostTrustedInvokersApiInvokerIdUpdate(ctx echo.Context, apiInvokerId string) error {
277         var serviceSecurity securityapi.ServiceSecurity
278
279         errMsg := "Unable to update service security context due to %s"
280
281         if err := ctx.Bind(&serviceSecurity); err != nil {
282                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, "invalid format for service security context"))
283         }
284
285         if err := serviceSecurity.Validate(); err != nil {
286                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
287         }
288
289         if _, ok := s.trustedInvokers[apiInvokerId]; ok {
290                 s.updateTrustedInvoker(serviceSecurity, apiInvokerId)
291         } else {
292                 return sendCoreError(ctx, http.StatusNotFound, "the invoker is not register as a trusted invoker")
293         }
294
295         uri := ctx.Request().Host + ctx.Request().URL.String()
296         ctx.Response().Header().Set(echo.HeaderLocation, ctx.Scheme()+`://`+path.Join(uri, apiInvokerId))
297
298         err := ctx.JSON(http.StatusOK, s.trustedInvokers[apiInvokerId])
299         if err != nil {
300                 // Something really bad happened, tell Echo that our handler failed
301                 return err
302         }
303
304         return nil
305 }
306
307 func (s *Security) updateTrustedInvoker(serviceSecurity securityapi.ServiceSecurity, invokerId string) {
308         s.lock.Lock()
309         defer s.lock.Unlock()
310         s.trustedInvokers[invokerId] = serviceSecurity
311 }
312
313 func sendAccessTokenError(ctx echo.Context, code int, err securityapi.AccessTokenErrError, message string) error {
314         accessTokenErr := securityapi.AccessTokenErr{
315                 Error:            err,
316                 ErrorDescription: &message,
317         }
318         return ctx.JSON(code, accessTokenErr)
319 }
320
321 // This function wraps sending of an error in the Error format, and
322 // handling the failure to marshal that.
323 func sendCoreError(ctx echo.Context, code int, message string) error {
324         pd := common29122.ProblemDetails{
325                 Cause:  &message,
326                 Status: &code,
327         }
328         err := ctx.JSON(code, pd)
329         return err
330 }