package server import ( "bytes" "context" "encoding/json" "errors" "fmt" "io" "net/http" "edge-infra.dev/pkg/lib/fog" "edge-infra.dev/pkg/sds/emergencyaccess/apierror" errorhandler "edge-infra.dev/pkg/sds/emergencyaccess/apierror/handler" eamiddleware "edge-infra.dev/pkg/sds/emergencyaccess/middleware" "edge-infra.dev/pkg/sds/emergencyaccess/msgdata" "edge-infra.dev/pkg/sds/emergencyaccess/remotecli" "edge-infra.dev/pkg/sds/emergencyaccess/requestservice" "edge-infra.dev/pkg/sds/emergencyaccess/types" "github.com/gin-gonic/gin" ) func (server *GatewayServer) SendCommand(c *gin.Context) { log := fog.FromContext(c) // Extract and validate payload payload, err := extractSendPayload(c) if err != nil { // extractSendPayload should return an apierror errorhandler.ErrorHandler(c, err) return } log = log.WithValues("sessionID", payload.SessionID) commandID := eamiddleware.GetCorrelationID(c) log = log.WithValues("commandID", commandID) c.Request = c.Request.Clone(fog.IntoContext(c.Request.Context(), log)) log.Info("Using correlation ID as commandID") user, ok := types.UserFromContext(c) if !ok { errorhandler.ErrorHandler(c, apierror.E(apierror.ErrSendFailure, fmt.Errorf("error retrieving user details"))) return } auditLog := log.WithValues( "command", payload.Command, "userID", user.Username, "requestID", commandID, "targetBannerUUID", payload.Target.BannerID(), "targetStoreUUID", payload.Target.StoreID(), "targetTerminalUUID", payload.Target.TerminalID(), "targetProjectID", payload.Target.ProjectID(), "darkmode", payload.AuthDetails.DarkMode, ) auditLog.Info("Send API Called") request, err := server.requestService.CreateRequest(c, payload.Command, requestservice.Config{ Target: payload.Target, }) if err != nil { errorhandler.ErrorHandler(c, apierror.E( apierror.ErrSendFailure, err, )) return } request, err = server.authorizeRequest(c, request, payload.Target) if err != nil { errorhandler.ErrorHandler(c, apierror.E( apierror.ErrSendFailure, err, )) return } log.Info("Command request authorized", "command", payload.Command, "user", user.Username) // Send valid command log.Info("Invoking Remotecli Send method") err = server.rcli.Send(c, user.Username, payload.SessionID, commandID, request) if errors.Is(err, remotecli.ErrUnknownSession) { errorhandler.ErrorHandler(c, apierror.E(apierror.ErrEndInvalidSessionID, err, "unknown session ID")) return } if err != nil { errorhandler.ErrorHandler(c, apierror.E(apierror.ErrSendFailure, err)) return } c.Status(http.StatusOK) } // Extracts the sendPayload from the request body, or returns an apierror.APIError func extractSendPayload(c *gin.Context) (payload types.SendPayload, err error) { if err = c.ShouldBindJSON(&payload); err != nil { err = apierror.E(apierror.ErrPayloadStructure, err) return types.SendPayload{}, err } if err = payload.Validate(); err != nil { err = apierror.E(apierror.ErrPayloadProperties, err) return types.SendPayload{}, err } return payload, nil } // The payload structure required for the authorizerequest endpoint type authRequestPayload struct { Request struct { Data json.RawMessage Attributes map[string]string } Target types.Target } // The response payload structure for the authorizRequest endpoint type authRequestResponse struct { Request struct { Data json.RawMessage Attributes map[string]string } } // authorizeRequest calls the AuthService's authorizeRequest API and returns the // fully populated valid request func (server *GatewayServer) authorizeRequest(ctx context.Context, request msgdata.Request, target types.Target) (msgdata.Request, error) { log := fog.FromContext(ctx) url := server.authorizeRequestURL.String() d, err := request.Data() if err != nil { return nil, fmt.Errorf("error constructing request message data: %w", err) } data := json.RawMessage(d) payload, err := json.Marshal(authRequestPayload{ Request: struct { Data json.RawMessage Attributes map[string]string }{ Data: data, Attributes: request.Attributes(), }, Target: target, }) if err != nil { return nil, err } req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(payload)) if err != nil { return nil, fmt.Errorf("creating http request: %w", err) } log.Info("Invoking auth service", "url", url) resp, err := server.client.Do(req) if err != nil { return nil, fmt.Errorf("request error: %w", err) } defer resp.Body.Close() log.Info("Auth service response received", "url", url) if resp.StatusCode != http.StatusOK { return nil, apierror.E( apierror.ErrSendFailure, errorhandler.ParseJSONAPIError(resp.Body), fmt.Errorf("non-ok status from auth service authorizeRequest (%d)", resp.StatusCode), ) } bytes, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("reading auth service response: %w", err) } var respPayload authRequestResponse err = json.Unmarshal(bytes, &respPayload) if err != nil { return nil, fmt.Errorf("error parsing auth service response: %w", err) } return msgdata.NewRequest(respPayload.Request.Data, respPayload.Request.Attributes) }