Documentation
¶
Overview ¶
Package ginadapter integrates the module with gin and the wide-event logger: it runs the net/http middleware of the providers in a gin chain, registers their routes on a gin router, gates routes with authorization checks, and records authentication outcomes on the request's wide event.
It is the only package of the library that imports gin and go-wide-event-logger. It records on the wide event the wideevent.Middleware put into the request context and never emits one of its own; it records IDs and decisions, never usernames, emails, names, tokens or codes. Mount the wide-event middleware with RedactQuery, or the code and state of every OAuth callback reach your logs. Everything in it is safe for concurrent use.
Example ¶
package main
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"github.com/gin-gonic/gin"
wideevent "github.com/mxcd/go-wide-event-logger"
"github.com/govengers/auth"
"github.com/govengers/auth/authorization"
"github.com/govengers/auth/ginadapter"
)
func main() {
// requireAPI stands in for session.Guard.RequireAPI: it authenticates the request from its session.
requireAPI := func(next http.Handler) http.Handler {
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
identity := &auth.Identity{Provider: "password", Subject: request.Header.Get("X-Example-User"), Active: true}
if identity.Subject == "admin-1" {
identity.Roles = []string{auth.RoleAdmin}
}
next.ServeHTTP(writer, request.WithContext(auth.WithIdentity(request.Context(), identity)))
})
}
engine := gin.New()
engine.Use(wideevent.Middleware(wideevent.WithEmitter(wideevent.EmitterFunc(func(*wideevent.Event) {}))))
api := engine.Group("/api", ginadapter.Middleware(requireAPI))
api.GET("/reports", ginadapter.Require(authorization.IsAdmin()), func(ginContext *gin.Context) {
ginContext.String(http.StatusOK, "reports")
})
for _, subject := range []string{"admin-1", "user-7"} {
request := httptest.NewRequestWithContext(context.Background(), http.MethodGet, "/api/reports", nil)
request.Header.Set("X-Example-User", subject)
recorder := httptest.NewRecorder()
engine.ServeHTTP(recorder, request)
fmt.Println(subject, recorder.Code)
}
}
Output: admin-1 200 user-7 403
Index ¶
- func Middleware(middleware func(http.Handler) http.Handler) gin.HandlerFunc
- func Observe(ctx context.Context, outcome auth.Outcome)
- func RedactQuery(we *wideevent.Event, ginContext *gin.Context)
- func RegisterRoutes(router gin.IRoutes, routes []auth.Route)
- func Require(check authorization.Check) gin.HandlerFunc
- func RequireMethodPermission(resource string) gin.HandlerFunc
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Middleware ¶
Middleware runs net/http middleware in a gin chain: when it calls its next handler, ginContext.Request becomes the (possibly enriched) request and the rest of the chain runs inside it; otherwise the chain is aborted. Like a net/http chain it wraps once, here, so state the middleware keeps across requests survives. Each request finds its gin chain through its context: a middleware that passes on a request whose context does not derive from the one it got is answered 500. Gin handlers keep writing to ginContext.Writer, so a writer the middleware wraps for its next handler is not seen.
func Observe ¶
Observe is an auth.Observer that records the outcome on the request's wide event: auth.provider, auth.action, auth.result, the client code, reason and error of Err, degraded for a success with Err, the subject and user ID of the identity (the impersonator's subject and the impersonated subject while impersonating), and the request outcome when the outcome decided it. A session.Guard reports a refresh on the same event before the request's authenticate outcome, so a refresh outcome goes under auth.refresh.* instead, without the user ID: no key is set twice. For the same reason a session.Impersonator's outcome, which follows the Guard's, goes under auth.impersonation.*. Without a wide event in ctx it does nothing.
func RedactQuery ¶
RedactQuery is a wide-event pre-enricher: mount it with wideevent.Middleware(wideevent.WithPreEnricher(ginadapter.RedactQuery)), or the code and state of every OAuth callback reach your logs. The middleware records the raw query as request.query; RedactQuery sets it again with every parameter name kept and every value replaced by "redacted", cut to 512 bytes. The JSON emitters print the latest value of a key; the dev emitters print every value, the raw query included, so keep those to development or emit only the latest values. Call it first from your own pre-enricher when you need one.
func RegisterRoutes ¶
RegisterRoutes registers each route with router.Handle(route.Method, route.Path, gin.WrapH(route.Handler)), so gin route templates (and therefore metric labels) are the provider paths. Register on the engine root. Like gin it panics on a path that conflicts with one already registered, which is a wiring mistake at startup.
func Require ¶
func Require(check authorization.Check) gin.HandlerFunc
Require runs check against the request context: allowed continues; refused aborts with auth.ResponseFor (401/403/500) and records authorization.denied / authorization.reason / authorization.error and we.Failure(err). A nil check is a wiring mistake: every request is answered 500.
func RequireMethodPermission ¶
func RequireMethodPermission(resource string) gin.HandlerFunc
RequireMethodPermission is Require(authorization.HasPermission(resource, auth.AccessForMethod(method))) evaluated per request: one gate for a route group where GET, HEAD and OPTIONS need read access on resource and every other method write access. Same recording and responses as Require.
Types ¶
This section is empty.