NONRTRIC-946: Servicemanager - mock kong and capif as library
[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
96         var jwtToken keycloak.Jwttoken
97         var err error
98
99         if s.keycloak != nil {
100                 jwtToken, err = s.keycloak.GetToken("invokerrealm", data)
101                 if err != nil {
102                         return sendAccessTokenError(ctx, http.StatusBadRequest, securityapi.AccessTokenErrErrorUnauthorizedClient, err.Error())
103                 }
104         } else {
105                 return sendAccessTokenError(ctx, http.StatusBadRequest, securityapi.AccessTokenErrErrorUnauthorizedClient, "keycloak is nil")
106         }
107
108         accessTokenResp := securityapi.AccessTokenRsp{
109                 AccessToken: jwtToken.AccessToken,
110                 ExpiresIn:   common29122.DurationSec(jwtToken.ExpiresIn),
111                 Scope:       accessTokenReq.Scope,
112                 TokenType:   "Bearer",
113         }
114
115         err = ctx.JSON(http.StatusCreated, accessTokenResp)
116         if err != nil {
117                 // Something really bad happened, tell Echo that our handler failed
118                 return err
119         }
120
121         return nil
122 }
123
124 func (s *Security) DeleteTrustedInvokersApiInvokerId(ctx echo.Context, apiInvokerId string) error {
125         if _, ok := s.trustedInvokers[apiInvokerId]; ok {
126                 s.deleteTrustedInvoker(apiInvokerId)
127         }
128
129         return ctx.NoContent(http.StatusNoContent)
130 }
131
132 func (s *Security) deleteTrustedInvoker(apiInvokerId string) {
133         s.lock.Lock()
134         defer s.lock.Unlock()
135         delete(s.trustedInvokers, apiInvokerId)
136 }
137
138 func (s *Security) GetTrustedInvokersApiInvokerId(ctx echo.Context, apiInvokerId string, params securityapi.GetTrustedInvokersApiInvokerIdParams) error {
139
140         if trustedInvoker, ok := s.trustedInvokers[apiInvokerId]; ok {
141                 updatedInvoker := s.checkParams(trustedInvoker, params)
142                 if updatedInvoker != nil {
143                         err := ctx.JSON(http.StatusOK, updatedInvoker)
144                         if err != nil {
145                                 return err
146                         }
147                 }
148         } else {
149                 return sendCoreError(ctx, http.StatusNotFound, fmt.Sprintf("invoker %s not registered as trusted invoker", apiInvokerId))
150         }
151
152         return nil
153 }
154
155 func (s *Security) checkParams(trustedInvoker securityapi.ServiceSecurity, params securityapi.GetTrustedInvokersApiInvokerIdParams) *securityapi.ServiceSecurity {
156         emptyString := ""
157
158         var sendAuthenticationInfo = (params.AuthenticationInfo != nil) && *params.AuthenticationInfo
159         var sendAuthorizationInfo = (params.AuthorizationInfo != nil) && *params.AuthorizationInfo
160
161         if sendAuthenticationInfo && sendAuthorizationInfo {
162                 return &trustedInvoker
163         }
164
165         data, _ := copystructure.Copy(trustedInvoker)
166         updatedInvoker, ok := data.(securityapi.ServiceSecurity)
167         if !ok {
168                 return nil
169         }
170
171         if !sendAuthenticationInfo {
172                 for i := range updatedInvoker.SecurityInfo {
173                         updatedInvoker.SecurityInfo[i].AuthenticationInfo = &emptyString
174                 }
175         }
176         if !sendAuthorizationInfo {
177                 for i := range updatedInvoker.SecurityInfo {
178                         updatedInvoker.SecurityInfo[i].AuthorizationInfo = &emptyString
179                 }
180         }
181         return &updatedInvoker
182 }
183
184 func (s *Security) PutTrustedInvokersApiInvokerId(ctx echo.Context, apiInvokerId string) error {
185         errMsg := "Unable to update security context due to %s."
186
187         if !s.invokerRegister.IsInvokerRegistered(apiInvokerId) {
188                 return sendCoreError(ctx, http.StatusBadRequest, "Unable to update security context due to Invoker not registered")
189         }
190         serviceSecurity, err := getServiceSecurityFromRequest(ctx)
191         if err != nil {
192                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
193         }
194
195         if err := serviceSecurity.Validate(); err != nil {
196                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
197         }
198
199         err = s.prepareNewSecurityContext(&serviceSecurity, apiInvokerId)
200         if err != nil {
201                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
202         }
203
204         uri := ctx.Request().Host + ctx.Request().URL.String()
205         ctx.Response().Header().Set(echo.HeaderLocation, ctx.Scheme()+`://`+path.Join(uri, apiInvokerId))
206
207         err = ctx.JSON(http.StatusCreated, s.trustedInvokers[apiInvokerId])
208         if err != nil {
209                 // Something really bad happened, tell Echo that our handler failed
210                 return err
211         }
212
213         return nil
214 }
215
216 func getServiceSecurityFromRequest(ctx echo.Context) (securityapi.ServiceSecurity, error) {
217         var serviceSecurity securityapi.ServiceSecurity
218         err := ctx.Bind(&serviceSecurity)
219         if err != nil {
220                 return securityapi.ServiceSecurity{}, fmt.Errorf("invalid format for service security")
221         }
222         return serviceSecurity, nil
223 }
224
225 func (s *Security) prepareNewSecurityContext(newContext *securityapi.ServiceSecurity, apiInvokerId string) error {
226         s.lock.Lock()
227         defer s.lock.Unlock()
228
229         err := newContext.PrepareNewSecurityContext(s.publishRegister.GetAllPublishedServices())
230         if err != nil {
231                 return err
232         }
233
234         s.trustedInvokers[apiInvokerId] = *newContext
235         return nil
236 }
237
238 func (s *Security) PostTrustedInvokersApiInvokerIdDelete(ctx echo.Context, apiInvokerId string) error {
239         var notification securityapi.SecurityNotification
240
241         errMsg := "Unable to revoke invoker due to %s"
242
243         if err := ctx.Bind(&notification); err != nil {
244                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, "invalid format for security notification"))
245         }
246
247         if err := notification.Validate(); err != nil {
248                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
249         }
250
251         if ss, ok := s.trustedInvokers[apiInvokerId]; ok {
252                 securityInfoCopy := s.revokeTrustedInvoker(&ss, notification)
253
254                 if len(securityInfoCopy) == 0 {
255                         s.deleteTrustedInvoker(apiInvokerId)
256                 } else {
257                         ss.SecurityInfo = securityInfoCopy
258                         s.updateTrustedInvoker(ss, apiInvokerId)
259                 }
260
261         } else {
262                 return sendCoreError(ctx, http.StatusNotFound, "the invoker is not register as a trusted invoker")
263         }
264
265         return ctx.NoContent(http.StatusNoContent)
266
267 }
268
269 func (s *Security) revokeTrustedInvoker(ss *securityapi.ServiceSecurity, notification securityapi.SecurityNotification) []securityapi.SecurityInformation {
270
271         data, _ := copystructure.Copy(ss.SecurityInfo)
272         securityInfoCopy, _ := data.([]securityapi.SecurityInformation)
273
274         for i, context := range ss.SecurityInfo {
275                 if notification.AefId == context.AefId || slices.Contains(notification.ApiIds, *context.ApiId) {
276                         securityInfoCopy = append(securityInfoCopy[:i], securityInfoCopy[i+1:]...)
277                 }
278         }
279
280         return securityInfoCopy
281
282 }
283
284 func (s *Security) PostTrustedInvokersApiInvokerIdUpdate(ctx echo.Context, apiInvokerId string) error {
285         var serviceSecurity securityapi.ServiceSecurity
286
287         errMsg := "Unable to update service security context due to %s"
288
289         if err := ctx.Bind(&serviceSecurity); err != nil {
290                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, "invalid format for service security context"))
291         }
292
293         if err := serviceSecurity.Validate(); err != nil {
294                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
295         }
296
297         if _, ok := s.trustedInvokers[apiInvokerId]; ok {
298                 s.updateTrustedInvoker(serviceSecurity, apiInvokerId)
299         } else {
300                 return sendCoreError(ctx, http.StatusNotFound, "the invoker is not register as a trusted invoker")
301         }
302
303         uri := ctx.Request().Host + ctx.Request().URL.String()
304         ctx.Response().Header().Set(echo.HeaderLocation, ctx.Scheme()+`://`+path.Join(uri, apiInvokerId))
305
306         err := ctx.JSON(http.StatusOK, s.trustedInvokers[apiInvokerId])
307         if err != nil {
308                 // Something really bad happened, tell Echo that our handler failed
309                 return err
310         }
311
312         return nil
313 }
314
315 func (s *Security) updateTrustedInvoker(serviceSecurity securityapi.ServiceSecurity, invokerId string) {
316         s.lock.Lock()
317         defer s.lock.Unlock()
318         s.trustedInvokers[invokerId] = serviceSecurity
319 }
320
321 func sendAccessTokenError(ctx echo.Context, code int, err securityapi.AccessTokenErrError, message string) error {
322         accessTokenErr := securityapi.AccessTokenErr{
323                 Error:            err,
324                 ErrorDescription: &message,
325         }
326         return ctx.JSON(code, accessTokenErr)
327 }
328
329 // This function wraps sending of an error in the Error format, and
330 // handling the failure to marshal that.
331 func sendCoreError(ctx echo.Context, code int, message string) error {
332         pd := common29122.ProblemDetails{
333                 Cause:  &message,
334                 Status: &code,
335         }
336         err := ctx.JSON(code, pd)
337         return err
338 }