Skip to content

Commit

Permalink
Add function to set middleware and event type in one go (#16)
Browse files Browse the repository at this point in the history
This is a usability enhancement which allows us to set the audit
middlewqrae and the event type in one function call. Note that it is
only recommended this be used on a specific path definition, as audit
events are not meant to be shared and should be uniquely identifiable.

Signed-off-by: Juan Antonio Osorio <juan.osoriorobles@eu.equinix.com>
  • Loading branch information
JAORMX authored Apr 14, 2022
1 parent 00c0f0c commit e74aa52
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 10 deletions.
13 changes: 12 additions & 1 deletion docs/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,15 @@ r.GET("/foo", myGetHandler)

mdw.RegisterEventType("CreateFoo", http.MethodPost, "/foo")
r.POST("/foo", myGetHandler)
```
```

It's also possible to both set the audit middleware for a specific path and
set a specific audit event type for the path:

```golang
router.GET("/user/:name", mdw.AuditWithType("GetUserInfo"), userInfoHandler)
```

**NOTE**: It is not recommended to assign a default or shared event type
to all events as audit events need to be uniquely identifiable
actions.
20 changes: 18 additions & 2 deletions ginaudit/mdw.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,19 @@ func (m *Middleware) RegisterEventType(eventType, httpMethod, path string) {
m.eventTypeMap.Store(keyFromHTTPMethodAndPath(httpMethod, path), eventType)
}

// Audit returns a gin middleware that will audit the request.
// This uses the a pre-registered type for the event (see RegisterEventType).
// If no type is registered, the event type is the HTTP method and path.
func (m *Middleware) Audit() gin.HandlerFunc {
return m.AuditWithType("")
}

// AuditWithType returns a gin middleware that will audit the request.
// This uses the given type for the event.
// If the type is empty, the event type will try to use a pre-registered
// type (see RegisterEventType) and if it doesn't find it,
// it'll use the HTTP method and path.
func (m *Middleware) AuditWithType(t string) gin.HandlerFunc {
return func(c *gin.Context) {
method := c.Request.Method
path := c.Request.URL.Path
Expand All @@ -76,7 +88,7 @@ func (m *Middleware) Audit() gin.HandlerFunc {
c.Next()

event := auditevent.NewAuditEvent(
m.getEventType(method, path),
m.getEventType(t, method, path),
auditevent.EventSource{
Type: "IP",
// This already takes into account X-Forwarded-For and alike headers
Expand All @@ -94,7 +106,11 @@ func (m *Middleware) Audit() gin.HandlerFunc {
}
}

func (m *Middleware) getEventType(httpMethod, path string) string {
func (m *Middleware) getEventType(preferredType, httpMethod, path string) string {
if preferredType != "" {
return preferredType
}

key := keyFromHTTPMethodAndPath(httpMethod, path)
rawEventType, ok := m.eventTypeMap.Load(key)
if ok {
Expand Down
18 changes: 11 additions & 7 deletions ginaudit/mdw_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func getTestCases() []testCase {
{
"user request fails with unregistered event type and known user from header",
auditevent.NewAuditEvent(
"POST:/fails-with-user-header",
"AlwaysBreaks",
auditevent.EventSource{
Type: "IP",
Value: "127.0.0.1",
Expand Down Expand Up @@ -138,21 +138,25 @@ func setFixtures(t *testing.T, w io.Writer) *gin.Engine {
mdw := ginaudit.NewJSONMiddleware(comp, w)

r := gin.New()

// Writing to `fails-with-user-header` breaks the app
r.POST("/fails-with-user-header",
mdw.AuditWithType("AlwaysBreaks"),
func(ctx *gin.Context) {
ctx.AbortWithStatus(http.StatusInternalServerError)
})

// Everything after this will be audited
r.Use(mdw.Audit())

// allowed user with registered event type
mdw.RegisterEventType("MyEventType", "GET", "/ok")
mdw.RegisterEventType("MyEventType", http.MethodGet, "/ok")
r.GET("/ok", func(c *gin.Context) {
c.Set("jwt.user", "user-ozz")
c.Set("jwt.subject", "sub-ozz")
c.JSON(http.StatusOK, "ok")
})

// Writing to `/ok` breaks the app
r.POST("/fails-with-user-header", func(ctx *gin.Context) {
ctx.AbortWithStatus(http.StatusInternalServerError)
})

// denied with no user
r.GET("/denied", func(c *gin.Context) {
c.JSON(http.StatusForbidden, "denied")
Expand Down

0 comments on commit e74aa52

Please sign in to comment.