1 package server
2
3 import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "errors"
8 "fmt"
9 "io"
10 "net/http"
11
12 "edge-infra.dev/pkg/lib/fog"
13 "edge-infra.dev/pkg/sds/emergencyaccess/apierror"
14 errorhandler "edge-infra.dev/pkg/sds/emergencyaccess/apierror/handler"
15 eamiddleware "edge-infra.dev/pkg/sds/emergencyaccess/middleware"
16 "edge-infra.dev/pkg/sds/emergencyaccess/msgdata"
17 "edge-infra.dev/pkg/sds/emergencyaccess/remotecli"
18 "edge-infra.dev/pkg/sds/emergencyaccess/requestservice"
19 "edge-infra.dev/pkg/sds/emergencyaccess/types"
20
21 "github.com/gin-gonic/gin"
22 )
23
24 func (server *GatewayServer) SendCommand(c *gin.Context) {
25 log := fog.FromContext(c)
26
27
28 payload, err := extractSendPayload(c)
29 if err != nil {
30
31 errorhandler.ErrorHandler(c, err)
32 return
33 }
34 log = log.WithValues("sessionID", payload.SessionID)
35
36 commandID := eamiddleware.GetCorrelationID(c)
37 log = log.WithValues("commandID", commandID)
38
39 c.Request = c.Request.Clone(fog.IntoContext(c.Request.Context(), log))
40
41 log.Info("Using correlation ID as commandID")
42
43 user, ok := types.UserFromContext(c)
44 if !ok {
45 errorhandler.ErrorHandler(c, apierror.E(apierror.ErrSendFailure, fmt.Errorf("error retrieving user details")))
46 return
47 }
48
49 auditLog := log.WithValues(
50 "command", payload.Command,
51 "userID", user.Username,
52 "requestID", commandID,
53 "targetBannerUUID", payload.Target.BannerID(),
54 "targetStoreUUID", payload.Target.StoreID(),
55 "targetTerminalUUID", payload.Target.TerminalID(),
56 "targetProjectID", payload.Target.ProjectID(),
57 "darkmode", payload.AuthDetails.DarkMode,
58 )
59 auditLog.Info("Send API Called")
60
61 request, err := server.requestService.CreateRequest(c, payload.Command, requestservice.Config{
62 Target: payload.Target,
63 })
64 if err != nil {
65 errorhandler.ErrorHandler(c, apierror.E(
66 apierror.ErrSendFailure,
67 err,
68 ))
69 return
70 }
71
72 request, err = server.authorizeRequest(c, request, payload.Target)
73 if err != nil {
74 errorhandler.ErrorHandler(c, apierror.E(
75 apierror.ErrSendFailure,
76 err,
77 ))
78 return
79 }
80
81 log.Info("Command request authorized", "command", payload.Command, "user", user.Username)
82
83
84 log.Info("Invoking Remotecli Send method")
85 err = server.rcli.Send(c, user.Username, payload.SessionID, commandID, request)
86 if errors.Is(err, remotecli.ErrUnknownSession) {
87 errorhandler.ErrorHandler(c, apierror.E(apierror.ErrEndInvalidSessionID, err, "unknown session ID"))
88 return
89 }
90 if err != nil {
91 errorhandler.ErrorHandler(c, apierror.E(apierror.ErrSendFailure, err))
92 return
93 }
94
95 c.Status(http.StatusOK)
96 }
97
98
99 func extractSendPayload(c *gin.Context) (payload types.SendPayload, err error) {
100 if err = c.ShouldBindJSON(&payload); err != nil {
101 err = apierror.E(apierror.ErrPayloadStructure, err)
102 return types.SendPayload{}, err
103 }
104 if err = payload.Validate(); err != nil {
105 err = apierror.E(apierror.ErrPayloadProperties, err)
106 return types.SendPayload{}, err
107 }
108 return payload, nil
109 }
110
111
112 type authRequestPayload struct {
113 Request struct {
114 Data json.RawMessage
115 Attributes map[string]string
116 }
117 Target types.Target
118 }
119
120
121 type authRequestResponse struct {
122 Request struct {
123 Data json.RawMessage
124 Attributes map[string]string
125 }
126 }
127
128
129
130 func (server *GatewayServer) authorizeRequest(ctx context.Context, request msgdata.Request, target types.Target) (msgdata.Request, error) {
131 log := fog.FromContext(ctx)
132
133 url := server.authorizeRequestURL.String()
134
135 d, err := request.Data()
136 if err != nil {
137 return nil, fmt.Errorf("error constructing request message data: %w", err)
138 }
139 data := json.RawMessage(d)
140
141 payload, err := json.Marshal(authRequestPayload{
142 Request: struct {
143 Data json.RawMessage
144 Attributes map[string]string
145 }{
146 Data: data,
147 Attributes: request.Attributes(),
148 },
149 Target: target,
150 })
151 if err != nil {
152 return nil, err
153 }
154
155 req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(payload))
156 if err != nil {
157 return nil, fmt.Errorf("creating http request: %w", err)
158 }
159
160 log.Info("Invoking auth service", "url", url)
161
162 resp, err := server.client.Do(req)
163 if err != nil {
164 return nil, fmt.Errorf("request error: %w", err)
165 }
166 defer resp.Body.Close()
167
168 log.Info("Auth service response received", "url", url)
169 if resp.StatusCode != http.StatusOK {
170 return nil, apierror.E(
171 apierror.ErrSendFailure,
172 errorhandler.ParseJSONAPIError(resp.Body),
173 fmt.Errorf("non-ok status from auth service authorizeRequest (%d)", resp.StatusCode),
174 )
175 }
176
177 bytes, err := io.ReadAll(resp.Body)
178 if err != nil {
179 return nil, fmt.Errorf("reading auth service response: %w", err)
180 }
181
182 var respPayload authRequestResponse
183 err = json.Unmarshal(bytes, &respPayload)
184 if err != nil {
185 return nil, fmt.Errorf("error parsing auth service response: %w", err)
186 }
187
188 return msgdata.NewRequest(respPayload.Request.Data, respPayload.Request.Attributes)
189 }
190
View as plain text