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
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
102 user, _ := types.UserFromContext(c)
103
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
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
137 requestID := eamiddleware.GetCorrelationID(c)
138
139 val, err := server.AuthService.AuthorizeCommand(c.Request.Context(), payload)
140
141 user, _ := types.UserFromContext(c)
142 userID := user.Username
143
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
174 target := payload.Target
175
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
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
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