...

Source file src/edge-infra.dev/pkg/sds/emergencyaccess/eagateway/server/send.go

Documentation: edge-infra.dev/pkg/sds/emergencyaccess/eagateway/server

     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  	// Extract and validate payload
    28  	payload, err := extractSendPayload(c)
    29  	if err != nil {
    30  		// extractSendPayload should return an apierror
    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  	// Send valid command
    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  // Extracts the sendPayload from the request body, or returns an apierror.APIError
    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  // The payload structure required for the authorizerequest endpoint
   112  type authRequestPayload struct {
   113  	Request struct {
   114  		Data       json.RawMessage
   115  		Attributes map[string]string
   116  	}
   117  	Target types.Target
   118  }
   119  
   120  // The response payload structure for the authorizRequest endpoint
   121  type authRequestResponse struct {
   122  	Request struct {
   123  		Data       json.RawMessage
   124  		Attributes map[string]string
   125  	}
   126  }
   127  
   128  // authorizeRequest calls the AuthService's authorizeRequest API and returns the
   129  // fully populated valid request
   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