...

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

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

     1  package server
     2  
     3  import (
     4  	"context"
     5  	"flag"
     6  	"fmt"
     7  	"net/http"
     8  	"os"
     9  
    10  	"github.com/gin-contrib/requestid"
    11  	"github.com/gin-gonic/gin"
    12  	"github.com/go-logr/logr"
    13  	"github.com/peterbourgon/ff/v3"
    14  
    15  	"edge-infra.dev/pkg/edge/api/middleware"
    16  	"edge-infra.dev/pkg/lib/fog"
    17  	"edge-infra.dev/pkg/sds/emergencyaccess/apierror"
    18  	errorhandler "edge-infra.dev/pkg/sds/emergencyaccess/apierror/handler"
    19  	"edge-infra.dev/pkg/sds/emergencyaccess/authservice"
    20  	"edge-infra.dev/pkg/sds/emergencyaccess/authservice/setup"
    21  	eamiddleware "edge-infra.dev/pkg/sds/emergencyaccess/middleware"
    22  	"edge-infra.dev/pkg/sds/emergencyaccess/msgdata"
    23  	"edge-infra.dev/pkg/sds/emergencyaccess/types"
    24  )
    25  
    26  type Authservice interface {
    27  	AuthorizeCommand(ctx context.Context, payload authservice.CommandAuthPayload) (authservice.Validation, error)
    28  	AuthorizeRequest(ctx context.Context, payload authservice.AuthorizeRequestPayload) (msgdata.Request, error)
    29  	AuthorizeTarget(ctx context.Context, target authservice.Target) error
    30  	AuthorizeUser(ctx context.Context) error
    31  	ResolveTarget(ctx context.Context, payload authservice.ResolveTargetPayload) (authservice.Target, error)
    32  }
    33  
    34  type Server struct {
    35  	GinEngine   *gin.Engine
    36  	AuthService Authservice
    37  	Log         logr.Logger
    38  }
    39  
    40  func New(router *gin.Engine, log logr.Logger, authService Authservice, checks ...func() error) Server {
    41  	server := Server{
    42  		GinEngine:   router,
    43  		Log:         log,
    44  		AuthService: authService,
    45  	}
    46  	server.newGinServer(checks...)
    47  	return server
    48  }
    49  
    50  func (server *Server) newGinServer(checks ...func() error) {
    51  	router := server.GinEngine
    52  	router.ContextWithFallback = true
    53  
    54  	router.Use(middleware.SetRequestContext())
    55  	router.Use(eamiddleware.SaveAuthToContext())
    56  	router.Use(gin.Recovery())
    57  
    58  	router.Any("/ready", func(c *gin.Context) {
    59  		c.String(http.StatusOK, "ok")
    60  	})
    61  
    62  	router.Any("/health", eamiddleware.HealthCheck(checks...))
    63  
    64  	public := router.Group("/")
    65  	public.Use(requestid.New(requestid.WithCustomHeaderStrKey(eamiddleware.CorrelationIDKey)))
    66  	public.Use(eamiddleware.SetLoggerInContext(server.Log))
    67  	public.Use(eamiddleware.RequestBookendLogs())
    68  	public.Use(eamiddleware.VerifyUserDetailsInContext())
    69  
    70  	public.POST("/authorizeUser", server.authorizeUser)
    71  	public.POST("/authorizeCommand", server.authorizeCommand)
    72  	public.POST("/authorizeRequest", server.authorizeRequest)
    73  	public.POST("/authorizeTarget", server.authorizeTarget)
    74  	public.POST("/resolveTarget", server.resolveTarget)
    75  }
    76  
    77  func (server Server) authorizeRequest(c *gin.Context) {
    78  	log := fog.FromContext(c)
    79  
    80  	// Checks incoming payload is valid.
    81  	var payload authservice.AuthorizeRequestPayload
    82  	if err := c.ShouldBindJSON(&payload); err != nil {
    83  		errorhandler.ErrorHandler(c, apierror.E(apierror.ErrPayloadStructure, err))
    84  		return
    85  	}
    86  	if err := payload.Validate(); err != nil {
    87  		errorhandler.ErrorHandler(c, apierror.E(apierror.ErrPayloadProperties, err))
    88  		return
    89  	}
    90  
    91  	log = log.WithValues(
    92  		"targetProjectID", payload.Target.ProjectID,
    93  		"targetBannerUUID", payload.Target.BannerID,
    94  		"targetStoreUUID", payload.Target.StoreID,
    95  		"targetTerminalUUID", payload.Target.TerminalID,
    96  	)
    97  	c.Request = c.Request.Clone(fog.IntoContext(c.Request.Context(), log))
    98  
    99  	req, err := server.AuthService.AuthorizeRequest(c.Request.Context(), payload)
   100  
   101  	// If no user in context, use empty string
   102  	user, _ := types.UserFromContext(c)
   103  	// log regardless of result of call
   104  	log.Info("Authorize Request Called",
   105  		"request", payload.Request,
   106  		"requestID", eamiddleware.GetCorrelationID(c),
   107  		"userID", user.Username,
   108  		"commandAuthorized", (err == nil),
   109  	)
   110  	if err != nil {
   111  		errorhandler.ErrorHandler(c, apierror.E(apierror.ErrSendFailure, err))
   112  		return
   113  	}
   114  	c.JSON(http.StatusOK, map[string]msgdata.Request{
   115  		"request": req,
   116  	})
   117  }
   118  
   119  func (server Server) authorizeCommand(c *gin.Context) {
   120  	log := fog.FromContext(c)
   121  
   122  	// Checks incoming payload is valid.
   123  	var payload authservice.CommandAuthPayload
   124  	if err := c.ShouldBindJSON(&payload); err != nil {
   125  		errorhandler.ErrorHandler(c, apierror.E(apierror.ErrPayloadStructure, err))
   126  		return
   127  	}
   128  	if err := payload.Validate(); err != nil {
   129  		errorhandler.ErrorHandler(c, apierror.E(apierror.ErrPayloadProperties, err))
   130  		return
   131  	}
   132  
   133  	log = log.WithValues("command", payload.Command, "targetBannerUUID", payload.Target.BannerID)
   134  	c.Request = c.Request.Clone(fog.IntoContext(c.Request.Context(), log))
   135  
   136  	// Currently the message commandID/requestID is set to the incoming correlationID
   137  	requestID := eamiddleware.GetCorrelationID(c)
   138  
   139  	val, err := server.AuthService.AuthorizeCommand(c.Request.Context(), payload)
   140  	// If no user in context, use empty string
   141  	user, _ := types.UserFromContext(c)
   142  	userID := user.Username
   143  	// log regardless of result of call
   144  	log.Info("Authorize Command Called",
   145  		"requestID", requestID,
   146  		"userID", userID,
   147  		"targetProjectID", payload.Target.ProjectID,
   148  		"targetStoreUUID", payload.Target.StoreID,
   149  		"targetTerminalUUID", payload.Target.TerminalID,
   150  		"commandAuthorized", val,
   151  		"darkmode", payload.AuthDetails.DarkMode,
   152  	)
   153  	if err != nil {
   154  		errorhandler.ErrorHandler(c, apierror.E(apierror.ErrSendFailure, err))
   155  		return
   156  	}
   157  
   158  	c.JSON(http.StatusOK, val)
   159  }
   160  
   161  func (server *Server) authorizeTarget(c *gin.Context) {
   162  	var payload authservice.AuthorizeTargetPayload
   163  
   164  	if err := c.ShouldBindJSON(&payload); err != nil {
   165  		errorhandler.ErrorHandler(c, apierror.E(apierror.ErrPayloadStructure, err))
   166  		return
   167  	}
   168  	if err := payload.Validate(); err != nil {
   169  		errorhandler.ErrorHandler(c, apierror.E(apierror.ErrPayloadProperties, err))
   170  		return
   171  	}
   172  	log := fog.FromContext(c)
   173  	// log user provided target
   174  	target := payload.Target
   175  	// If no user in context, use empty string
   176  	user, _ := types.UserFromContext(c)
   177  	userID := user.Username
   178  	defer func() {
   179  		log.Info("Authorize Target Called",
   180  			"userID", userID,
   181  			"targetProjectID", target.ProjectID,
   182  			"targetBannerUUID", target.BannerID,
   183  			"targetStoreUUID", target.StoreID,
   184  			"targetTerminalUUID", target.TerminalID,
   185  		)
   186  	}()
   187  	err := server.AuthService.AuthorizeTarget(c, payload.Target)
   188  	if err != nil {
   189  		errorhandler.ErrorHandler(c, apierror.E(apierror.ErrAuthFailure, err))
   190  		return
   191  	}
   192  	c.Status(http.StatusOK)
   193  }
   194  
   195  func (server *Server) resolveTarget(c *gin.Context) {
   196  	var payload authservice.ResolveTargetPayload
   197  	if err := c.ShouldBindJSON(&payload); err != nil {
   198  		errorhandler.ErrorHandler(c, apierror.E(apierror.ErrPayloadStructure, err))
   199  		return
   200  	}
   201  	if err := payload.Validate(); err != nil {
   202  		errorhandler.ErrorHandler(c, apierror.E(apierror.ErrPayloadProperties, err))
   203  		return
   204  	}
   205  
   206  	var err error
   207  
   208  	target, err := server.AuthService.ResolveTarget(c.Request.Context(), payload)
   209  	if err != nil {
   210  		errorhandler.ErrorHandler(c, apierror.E(apierror.ErrAuthFailure, err))
   211  		return
   212  	}
   213  	payload.Target = target
   214  
   215  	c.JSON(http.StatusOK, payload)
   216  }
   217  
   218  func (server Server) authorizeUser(c *gin.Context) {
   219  	log := fog.FromContext(c)
   220  	// If no user in context, use empty string
   221  	user, _ := types.UserFromContext(c)
   222  	userID := user.Username
   223  	auditLog := log.WithValues(
   224  		"userID", userID,
   225  	)
   226  
   227  	if err := server.AuthService.AuthorizeUser(c); err != nil {
   228  		auditLog.Info("Authorize User Called", "authorized", false)
   229  		errorhandler.ErrorHandler(c, apierror.E(apierror.ErrAuthFailure, err))
   230  		return
   231  	}
   232  	auditLog.Info("Authorize User Called", "authorized", true)
   233  	c.Status(http.StatusOK)
   234  }
   235  
   236  func Run() error {
   237  	router := gin.New()
   238  	log := newLogger()
   239  
   240  	config := setup.Config{}
   241  	flags := flag.NewFlagSet("ea-authservice", flag.ExitOnError)
   242  	config.BindFlags(flags)
   243  	// flags passed as --user-service-host from cli or USER_SERVICE_HOST in env will be parsed here.
   244  	if err := ff.Parse(flags, os.Args[1:], ff.WithEnvVarNoPrefix(), ff.WithIgnoreUndefined(true)); err != nil {
   245  		return err
   246  	}
   247  
   248  	if err := config.AuthService.Validate(); err != nil {
   249  		return fmt.Errorf("invalid authservice configuration: %w", err)
   250  	}
   251  
   252  	authService, checks, err := setup.CreateAuthservice(log, config)
   253  	if err != nil {
   254  		return err
   255  	}
   256  
   257  	server := New(router, log, authService, checks...)
   258  
   259  	return server.GinEngine.Run()
   260  }
   261  

View as plain text