7f5782efffba4978bd30f77a7343c5a758edfdcd
[nonrtric/plt/sme.git] / capifcore / internal / invokermanagement / invokermanagement.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 invokermanagement
22
23 import (
24         "net/http"
25         "path"
26         "strconv"
27         "strings"
28         "sync"
29
30         publishapi "oransc.org/nonrtric/capifcore/internal/publishserviceapi"
31
32         "oransc.org/nonrtric/capifcore/internal/common29122"
33         invokerapi "oransc.org/nonrtric/capifcore/internal/invokermanagementapi"
34
35         "oransc.org/nonrtric/capifcore/internal/publishservice"
36
37         "github.com/labstack/echo/v4"
38 )
39
40 //go:generate mockery --name InvokerRegister
41 type InvokerRegister interface {
42         IsInvokerRegistered(invokerId string) bool
43         VerifyInvokerSecret(invokerId, secret string) bool
44         GetInvokerApiList(invokerId string) *invokerapi.APIList
45 }
46
47 type InvokerManager struct {
48         onboardedInvokers map[string]invokerapi.APIInvokerEnrolmentDetails
49         publishRegister   publishservice.PublishRegister
50         nextId            int64
51         lock              sync.Mutex
52 }
53
54 func NewInvokerManager(publishRegister publishservice.PublishRegister) *InvokerManager {
55         return &InvokerManager{
56                 onboardedInvokers: make(map[string]invokerapi.APIInvokerEnrolmentDetails),
57                 publishRegister:   publishRegister,
58                 nextId:            1000,
59         }
60 }
61
62 func (im *InvokerManager) IsInvokerRegistered(invokerId string) bool {
63         im.lock.Lock()
64         defer im.lock.Unlock()
65
66         _, registered := im.onboardedInvokers[invokerId]
67         return registered
68 }
69
70 func (im *InvokerManager) VerifyInvokerSecret(invokerId, secret string) bool {
71         im.lock.Lock()
72         defer im.lock.Unlock()
73
74         verified := false
75         if invoker, registered := im.onboardedInvokers[invokerId]; registered {
76                 verified = *invoker.OnboardingInformation.OnboardingSecret == secret
77         }
78         return verified
79 }
80
81 func (im *InvokerManager) GetInvokerApiList(invokerId string) *invokerapi.APIList {
82         invoker, ok := im.onboardedInvokers[invokerId]
83         if ok {
84                 return invoker.ApiList
85         }
86         return nil
87 }
88
89 func (im *InvokerManager) PostOnboardedInvokers(ctx echo.Context) error {
90         var newInvoker invokerapi.APIInvokerEnrolmentDetails
91         err := ctx.Bind(&newInvoker)
92         if err != nil {
93                 return sendCoreError(ctx, http.StatusBadRequest, "Invalid format for invoker")
94         }
95
96         shouldReturn, coreError := im.validateInvoker(newInvoker, ctx)
97         if shouldReturn {
98                 return coreError
99         }
100
101         im.lock.Lock()
102         defer im.lock.Unlock()
103
104         newInvoker.ApiInvokerId = im.getId(newInvoker.ApiInvokerInformation)
105         onboardingSecret := "onboarding_secret_"
106         if newInvoker.ApiInvokerInformation != nil {
107                 onboardingSecret = onboardingSecret + strings.ReplaceAll(*newInvoker.ApiInvokerInformation, " ", "_")
108         } else {
109                 onboardingSecret = onboardingSecret + *newInvoker.ApiInvokerId
110         }
111         newInvoker.OnboardingInformation.OnboardingSecret = &onboardingSecret
112
113         im.onboardedInvokers[*newInvoker.ApiInvokerId] = newInvoker
114
115         uri := ctx.Request().Host + ctx.Request().URL.String()
116         ctx.Response().Header().Set(echo.HeaderLocation, ctx.Scheme()+`://`+path.Join(uri, *newInvoker.ApiInvokerId))
117         err = ctx.JSON(http.StatusCreated, newInvoker)
118         if err != nil {
119                 // Something really bad happened, tell Echo that our handler failed
120                 return err
121         }
122
123         return nil
124 }
125
126 func (im *InvokerManager) DeleteOnboardedInvokersOnboardingId(ctx echo.Context, onboardingId string) error {
127         im.lock.Lock()
128         defer im.lock.Unlock()
129
130         delete(im.onboardedInvokers, onboardingId)
131
132         return ctx.NoContent(http.StatusNoContent)
133 }
134
135 func (im *InvokerManager) PutOnboardedInvokersOnboardingId(ctx echo.Context, onboardingId string) error {
136         var invoker invokerapi.APIInvokerEnrolmentDetails
137         err := ctx.Bind(&invoker)
138         if err != nil {
139                 return sendCoreError(ctx, http.StatusBadRequest, "Invalid format for invoker")
140         }
141
142         if onboardingId != *invoker.ApiInvokerId {
143                 return sendCoreError(ctx, http.StatusBadRequest, "Invoker ApiInvokerId not matching")
144         }
145
146         shouldReturn, coreError := im.validateInvoker(invoker, ctx)
147         if shouldReturn {
148                 return coreError
149         }
150
151         im.lock.Lock()
152         defer im.lock.Unlock()
153
154         if _, ok := im.onboardedInvokers[onboardingId]; ok {
155                 im.onboardedInvokers[*invoker.ApiInvokerId] = invoker
156         } else {
157                 return sendCoreError(ctx, http.StatusNotFound, "The invoker to update has not been onboarded")
158         }
159
160         err = ctx.JSON(http.StatusOK, invoker)
161         if err != nil {
162                 // Something really bad happened, tell Echo that our handler failed
163                 return err
164         }
165
166         return nil
167 }
168
169 func (im *InvokerManager) ModifyIndApiInvokeEnrolment(ctx echo.Context, onboardingId string) error {
170         return ctx.NoContent(http.StatusNotImplemented)
171 }
172
173 func (im *InvokerManager) validateInvoker(invoker invokerapi.APIInvokerEnrolmentDetails, ctx echo.Context) (bool, error) {
174         if invoker.NotificationDestination == "" {
175                 return true, sendCoreError(ctx, http.StatusBadRequest, "Invoker missing required NotificationDestination")
176         }
177
178         if invoker.OnboardingInformation.ApiInvokerPublicKey == "" {
179                 return true, sendCoreError(ctx, http.StatusBadRequest, "Invoker missing required OnboardingInformation.ApiInvokerPublicKey")
180         }
181
182         if !im.areAPIsPublished(invoker.ApiList) {
183                 return true, sendCoreError(ctx, http.StatusBadRequest, "Some APIs needed by invoker are not registered")
184         }
185
186         return false, nil
187 }
188
189 func (im *InvokerManager) areAPIsPublished(apis *invokerapi.APIList) bool {
190         if apis == nil {
191                 return true
192         }
193         return im.publishRegister.AreAPIsPublished((*[]publishapi.ServiceAPIDescription)(apis))
194 }
195
196 func (im *InvokerManager) getId(invokerInfo *string) *string {
197         idAsString := "api_invoker_id_"
198         if invokerInfo != nil {
199                 idAsString = idAsString + strings.ReplaceAll(*invokerInfo, " ", "_")
200         } else {
201                 idAsString = idAsString + strconv.FormatInt(im.nextId, 10)
202                 im.nextId = im.nextId + 1
203         }
204         return &idAsString
205 }
206
207 // This function wraps sending of an error in the Error format, and
208 // handling the failure to marshal that.
209 func sendCoreError(ctx echo.Context, code int, message string) error {
210         pd := common29122.ProblemDetails{
211                 Cause:  &message,
212                 Status: &code,
213         }
214         err := ctx.JSON(code, pd)
215         return err
216 }