Integration with keycloak, add client
[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         err = s.keycloak.AddClient(apiInvokerId, "invokerrealm")
196         if err != nil {
197                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
198         }
199
200         uri := ctx.Request().Host + ctx.Request().URL.String()
201         ctx.Response().Header().Set(echo.HeaderLocation, ctx.Scheme()+`://`+path.Join(uri, apiInvokerId))
202
203         err = ctx.JSON(http.StatusCreated, s.trustedInvokers[apiInvokerId])
204         if err != nil {
205                 // Something really bad happened, tell Echo that our handler failed
206                 return err
207         }
208
209         return nil
210 }
211
212 func getServiceSecurityFromRequest(ctx echo.Context) (securityapi.ServiceSecurity, error) {
213         var serviceSecurity securityapi.ServiceSecurity
214         err := ctx.Bind(&serviceSecurity)
215         if err != nil {
216                 return securityapi.ServiceSecurity{}, fmt.Errorf("invalid format for service security")
217         }
218         return serviceSecurity, nil
219 }
220
221 func (s *Security) prepareNewSecurityContext(newContext *securityapi.ServiceSecurity, apiInvokerId string) error {
222         s.lock.Lock()
223         defer s.lock.Unlock()
224
225         err := newContext.PrepareNewSecurityContext(s.publishRegister.GetAllPublishedServices())
226         if err != nil {
227                 return err
228         }
229
230         s.trustedInvokers[apiInvokerId] = *newContext
231         return nil
232 }
233
234 func (s *Security) PostTrustedInvokersApiInvokerIdDelete(ctx echo.Context, apiInvokerId string) error {
235         var notification securityapi.SecurityNotification
236
237         errMsg := "Unable to revoke invoker due to %s"
238
239         if err := ctx.Bind(&notification); err != nil {
240                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, "invalid format for security notification"))
241         }
242
243         if err := notification.Validate(); err != nil {
244                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
245         }
246
247         if ss, ok := s.trustedInvokers[apiInvokerId]; ok {
248                 securityInfoCopy := s.revokeTrustedInvoker(&ss, notification, apiInvokerId)
249
250                 if len(securityInfoCopy) == 0 {
251                         s.deleteTrustedInvoker(apiInvokerId)
252                 } else {
253                         ss.SecurityInfo = securityInfoCopy
254                         s.updateTrustedInvoker(ss, apiInvokerId)
255                 }
256
257         } else {
258                 return sendCoreError(ctx, http.StatusNotFound, "the invoker is not register as a trusted invoker")
259         }
260
261         return ctx.NoContent(http.StatusNoContent)
262
263 }
264
265 func (s *Security) revokeTrustedInvoker(ss *securityapi.ServiceSecurity, notification securityapi.SecurityNotification, apiInvokerId string) []securityapi.SecurityInformation {
266
267         data, _ := copystructure.Copy(ss.SecurityInfo)
268         securityInfoCopy, _ := data.([]securityapi.SecurityInformation)
269
270         for i, context := range ss.SecurityInfo {
271                 if notification.AefId == context.AefId || slices.Contains(notification.ApiIds, *context.ApiId) {
272                         securityInfoCopy = append(securityInfoCopy[:i], securityInfoCopy[i+1:]...)
273                 }
274         }
275
276         return securityInfoCopy
277
278 }
279
280 func (s *Security) PostTrustedInvokersApiInvokerIdUpdate(ctx echo.Context, apiInvokerId string) error {
281         var serviceSecurity securityapi.ServiceSecurity
282
283         errMsg := "Unable to update service security context due to %s"
284
285         if err := ctx.Bind(&serviceSecurity); err != nil {
286                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, "invalid format for service security context"))
287         }
288
289         if err := serviceSecurity.Validate(); err != nil {
290                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
291         }
292
293         if _, ok := s.trustedInvokers[apiInvokerId]; ok {
294                 s.updateTrustedInvoker(serviceSecurity, apiInvokerId)
295         } else {
296                 return sendCoreError(ctx, http.StatusNotFound, "the invoker is not register as a trusted invoker")
297         }
298
299         uri := ctx.Request().Host + ctx.Request().URL.String()
300         ctx.Response().Header().Set(echo.HeaderLocation, ctx.Scheme()+`://`+path.Join(uri, apiInvokerId))
301
302         err := ctx.JSON(http.StatusOK, s.trustedInvokers[apiInvokerId])
303         if err != nil {
304                 // Something really bad happened, tell Echo that our handler failed
305                 return err
306         }
307
308         return nil
309 }
310
311 func (s *Security) updateTrustedInvoker(serviceSecurity securityapi.ServiceSecurity, invokerId string) {
312         s.lock.Lock()
313         defer s.lock.Unlock()
314         s.trustedInvokers[invokerId] = serviceSecurity
315 }
316
317 func sendAccessTokenError(ctx echo.Context, code int, err securityapi.AccessTokenErrError, message string) error {
318         accessTokenErr := securityapi.AccessTokenErr{
319                 Error:            err,
320                 ErrorDescription: &message,
321         }
322         return ctx.JSON(code, accessTokenErr)
323 }
324
325 // This function wraps sending of an error in the Error format, and
326 // handling the failure to marshal that.
327 func sendCoreError(ctx echo.Context, code int, message string) error {
328         pd := common29122.ProblemDetails{
329                 Cause:  &message,
330                 Status: &code,
331         }
332         err := ctx.JSON(code, pd)
333         return err
334 }