NONRTRIC-946: Get Allowed Publishers
[nonrtric/plt/sme.git] / capifcore / internal / invokermanagement / invokermanagement.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 invokermanagement
23
24 import (
25         "fmt"
26         "net/http"
27         "path"
28         "sync"
29
30         "oransc.org/nonrtric/capifcore/internal/eventsapi"
31         "oransc.org/nonrtric/capifcore/internal/keycloak"
32
33         "oransc.org/nonrtric/capifcore/internal/common29122"
34         invokerapi "oransc.org/nonrtric/capifcore/internal/invokermanagementapi"
35         "oransc.org/nonrtric/capifcore/internal/publishservice"
36
37         echo "github.com/labstack/echo/v4"
38 )
39
40 //go:generate mockery --name InvokerRegister
41 type InvokerRegister interface {
42         // Checks if the invoker is registered.
43         // Returns true of the provided invoker is registered, false otherwise.
44         IsInvokerRegistered(invokerId string) bool
45         // Verifies that the provided secret is the invoker's registered secret.
46         // Returns true if the provided secret is the registered invoker's secret, false otherwise.
47         VerifyInvokerSecret(invokerId, secret string) bool
48         // Gets the provided invoker's registered APIs.
49         // Returns a list of all the invoker's registered APIs.
50         GetInvokerApiList(invokerId string) *invokerapi.APIList
51 }
52
53 type InvokerManager struct {
54         onboardedInvokers map[string]invokerapi.APIInvokerEnrolmentDetails
55         publishRegister   publishservice.PublishRegister
56         nextId            int64
57         keycloak          keycloak.AccessManagement
58         eventChannel      chan<- eventsapi.EventNotification
59         lock              sync.Mutex
60 }
61
62 // Creates a manager that implements both the InvokerRegister and the invokermanagementapi.ServerInterface interfaces.
63 func NewInvokerManager(publishRegister publishservice.PublishRegister, km keycloak.AccessManagement, eventChannel chan<- eventsapi.EventNotification) *InvokerManager {
64         return &InvokerManager{
65                 onboardedInvokers: make(map[string]invokerapi.APIInvokerEnrolmentDetails),
66                 publishRegister:   publishRegister,
67                 nextId:            1000,
68                 keycloak:          km,
69                 eventChannel:      eventChannel,
70         }
71 }
72
73 func (im *InvokerManager) IsInvokerRegistered(invokerId string) bool {
74         im.lock.Lock()
75         defer im.lock.Unlock()
76
77         _, registered := im.onboardedInvokers[invokerId]
78         return registered
79 }
80
81 func (im *InvokerManager) VerifyInvokerSecret(invokerId, secret string) bool {
82         im.lock.Lock()
83         defer im.lock.Unlock()
84
85         verified := false
86         if invoker, registered := im.onboardedInvokers[invokerId]; registered {
87                 verified = *invoker.OnboardingInformation.OnboardingSecret == secret
88         }
89         return verified
90 }
91
92 func (im *InvokerManager) GetInvokerApiList(invokerId string) *invokerapi.APIList {
93         var apiList invokerapi.APIList = im.publishRegister.GetAllPublishedServices()
94         im.lock.Lock()
95         defer im.lock.Unlock()
96         invoker, ok := im.onboardedInvokers[invokerId]
97         if ok {
98                 invoker.ApiList = &apiList
99                 return &apiList
100         }
101         return nil
102 }
103
104 // Creates a new individual API Invoker profile.
105 func (im *InvokerManager) PostOnboardedInvokers(ctx echo.Context) error {
106         errMsg := "Unable to onboard invoker due to %s"
107
108         newInvoker, err := getInvokerFromRequest(ctx)
109         if err != nil {
110                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
111         }
112
113         if err = im.isInvokerOnboarded(newInvoker); err != nil {
114                 return sendCoreError(ctx, http.StatusForbidden, fmt.Sprintf(errMsg, err))
115         }
116
117         if err = im.validateInvoker(newInvoker, ctx); err != nil {
118                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
119         }
120
121         im.prepareNewInvoker(&newInvoker)
122
123         go im.sendEvent(*newInvoker.ApiInvokerId, eventsapi.CAPIFEventAPIINVOKERONBOARDED)
124
125         uri := ctx.Request().Host + ctx.Request().URL.String()
126         ctx.Response().Header().Set(echo.HeaderLocation, ctx.Scheme()+`://`+path.Join(uri, *newInvoker.ApiInvokerId))
127
128         err = ctx.JSON(http.StatusCreated, newInvoker)
129         if err != nil {
130                 // Something really bad happened, tell Echo that our handler failed
131                 return err
132         }
133
134         return nil
135 }
136
137 func (im *InvokerManager) isInvokerOnboarded(newInvoker invokerapi.APIInvokerEnrolmentDetails) error {
138         for _, invoker := range im.onboardedInvokers {
139                 if err := invoker.ValidateAlreadyOnboarded(newInvoker); err != nil {
140                         return err
141                 }
142         }
143         return nil
144 }
145
146 func (im *InvokerManager) prepareNewInvoker(newInvoker *invokerapi.APIInvokerEnrolmentDetails) {
147         var apiListRequestedServices invokerapi.APIList = nil
148         if newInvoker.ApiList != nil {
149                 apiListRequestedServices = *newInvoker.ApiList
150         }
151         var allowedPublishedServices invokerapi.APIList = im.publishRegister.GetAllowedPublishedServices(apiListRequestedServices)
152         newInvoker.ApiList = &allowedPublishedServices
153
154         im.lock.Lock()
155         defer im.lock.Unlock()
156
157         newInvoker.PrepareNewInvoker()
158         im.addClientInKeycloak(newInvoker)
159         im.onboardedInvokers[*newInvoker.ApiInvokerId] = *newInvoker
160 }
161
162 func (im *InvokerManager) addClientInKeycloak(newInvoker *invokerapi.APIInvokerEnrolmentDetails) error {
163         if err := im.keycloak.AddClient(*newInvoker.ApiInvokerId, "invokerrealm"); err != nil {
164                 return err
165         }
166
167         if body, err := im.keycloak.GetClientRepresentation(*newInvoker.ApiInvokerId, "invokerrealm"); err != nil {
168                 return err
169         } else {
170                 newInvoker.OnboardingInformation.OnboardingSecret = body.Secret
171         }
172         return nil
173 }
174
175 // Deletes an individual API Invoker.
176 func (im *InvokerManager) DeleteOnboardedInvokersOnboardingId(ctx echo.Context, onboardingId string) error {
177         if _, ok := im.onboardedInvokers[onboardingId]; ok {
178                 im.deleteInvoker(onboardingId)
179         }
180
181         go im.sendEvent(onboardingId, eventsapi.CAPIFEventAPIINVOKEROFFBOARDED)
182
183         return ctx.NoContent(http.StatusNoContent)
184 }
185
186 func (im *InvokerManager) deleteInvoker(onboardingId string) {
187         im.lock.Lock()
188         defer im.lock.Unlock()
189         delete(im.onboardedInvokers, onboardingId)
190 }
191
192 func getInvokerFromRequest(ctx echo.Context) (invokerapi.APIInvokerEnrolmentDetails, error) {
193         var invoker invokerapi.APIInvokerEnrolmentDetails
194         if err := ctx.Bind(&invoker); err != nil {
195                 return invokerapi.APIInvokerEnrolmentDetails{}, fmt.Errorf("invalid format for invoker")
196         }
197         return invoker, nil
198 }
199
200 // Updates an individual API invoker details.
201 func (im *InvokerManager) PutOnboardedInvokersOnboardingId(ctx echo.Context, onboardingId string) error {
202         errMsg := "Unable to update invoker due to %s"
203
204         newInvoker, err := getInvokerFromRequest(ctx)
205         if err != nil {
206                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
207         }
208
209         // Additional validation for PUT
210         if (newInvoker.ApiInvokerId == nil) || (*newInvoker.ApiInvokerId != onboardingId) {
211                 errMismatch := "APIInvokerEnrolmentDetails ApiInvokerId doesn't match path parameter"
212                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, errMismatch))
213         }
214
215         if err := im.validateInvoker(newInvoker, ctx); err != nil {
216                 return sendCoreError(ctx, http.StatusBadRequest, fmt.Sprintf(errMsg, err))
217         }
218
219         if _, ok := im.onboardedInvokers[onboardingId]; ok {
220                 im.updateInvoker(newInvoker)
221         } else {
222                 return sendCoreError(ctx, http.StatusNotFound, "The invoker to update has not been onboarded")
223         }
224
225         err = ctx.JSON(http.StatusOK, newInvoker)
226         if err != nil {
227                 // Something really bad happened, tell Echo that our handler failed
228                 return err
229         }
230
231         return nil
232 }
233
234 func (im *InvokerManager) updateInvoker(invoker invokerapi.APIInvokerEnrolmentDetails) {
235         im.lock.Lock()
236         defer im.lock.Unlock()
237         im.onboardedInvokers[*invoker.ApiInvokerId] = invoker
238 }
239
240 func (im *InvokerManager) ModifyIndApiInvokeEnrolment(ctx echo.Context, onboardingId string) error {
241         return ctx.NoContent(http.StatusNotImplemented)
242 }
243
244 func (im *InvokerManager) validateInvoker(invoker invokerapi.APIInvokerEnrolmentDetails, ctx echo.Context) error {
245         if err := invoker.Validate(); err != nil {
246                 return err
247         }
248
249         return nil
250 }
251
252 func (im *InvokerManager) sendEvent(invokerId string, eventType eventsapi.CAPIFEvent) {
253         invokerIds := []string{invokerId}
254         event := eventsapi.EventNotification{
255                 EventDetail: &eventsapi.CAPIFEventDetail{
256                         ApiInvokerIds: &invokerIds,
257                 },
258                 Events: eventType,
259         }
260         im.eventChannel <- event
261 }
262
263 // This function wraps sending of an error in the Error format, and
264 // handling the failure to marshal that.
265 func sendCoreError(ctx echo.Context, code int, message string) error {
266         pd := common29122.ProblemDetails{
267                 Cause:  &message,
268                 Status: &code,
269         }
270         err := ctx.JSON(code, pd)
271         return err
272 }