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