authelia/internal/middlewares/authelia_context.go
James Elliott 4dce8f9496
perf(authorizer): preload access control lists (#1640)
* adjust session refresh to always occur (for disabled users)

* feat: adds filtering option for Request Method in ACL's

* simplify flow of internal/authorization/authorizer.go's methods

* implement query string checking

* utilize authorizer.Object fully

* make matchers uniform

* add tests

* add missing request methods

* add frontend enhancements to handle request method

* add request method to 1FA Handler Suite

* add internal ACL representations (preparsing)

* expand on access_control next

* add docs

* remove unnecessary slice for network names and instead just use a plain string

* add warning for ineffectual bypass policy (due to subjects)

* add user/group wildcard support

* fix(authorization): allow subject rules to match anonymous users

* feat(api): add new params

* docs(api): wording adjustments

* test: add request method into testing and proxy docs

* test: add several checks and refactor schema validation for ACL

* test: add integration test for methods acl

* refactor: apply suggestions from code review

* docs(authorization): update description
2021-03-05 15:18:31 +11:00

184 lines
5.3 KiB
Go

package middlewares
import (
"encoding/json"
"fmt"
"net"
"strings"
"github.com/asaskevich/govalidator"
"github.com/sirupsen/logrus"
"github.com/valyala/fasthttp"
"github.com/authelia/authelia/internal/configuration/schema"
"github.com/authelia/authelia/internal/session"
"github.com/authelia/authelia/internal/utils"
)
// NewRequestLogger create a new request logger for the given request.
func NewRequestLogger(ctx *AutheliaCtx) *logrus.Entry {
return logrus.WithFields(logrus.Fields{
"method": string(ctx.Method()),
"path": string(ctx.Path()),
"remote_ip": ctx.RemoteIP().String(),
})
}
// NewAutheliaCtx instantiate an AutheliaCtx out of a RequestCtx.
func NewAutheliaCtx(ctx *fasthttp.RequestCtx, configuration schema.Configuration, providers Providers) (*AutheliaCtx, error) {
autheliaCtx := new(AutheliaCtx)
autheliaCtx.RequestCtx = ctx
autheliaCtx.Providers = providers
autheliaCtx.Configuration = configuration
autheliaCtx.Logger = NewRequestLogger(autheliaCtx)
autheliaCtx.Clock = utils.RealClock{}
return autheliaCtx, nil
}
// AutheliaMiddleware is wrapping the RequestCtx into an AutheliaCtx providing Authelia related objects.
func AutheliaMiddleware(configuration schema.Configuration, providers Providers) func(next RequestHandler) fasthttp.RequestHandler {
return func(next RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
autheliaCtx, err := NewAutheliaCtx(ctx, configuration, providers)
if err != nil {
autheliaCtx.Error(err, operationFailedMessage)
return
}
next(autheliaCtx)
}
}
}
// Error reply with an error and display the stack trace in the logs.
func (c *AutheliaCtx) Error(err error, message string) {
b, marshalErr := json.Marshal(ErrorResponse{Status: "KO", Message: message})
if marshalErr != nil {
c.Logger.Error(marshalErr)
}
c.SetContentType("application/json")
c.SetBody(b)
c.Logger.Error(err)
}
// ReplyError reply with an error but does not display any stack trace in the logs.
func (c *AutheliaCtx) ReplyError(err error, message string) {
b, marshalErr := json.Marshal(ErrorResponse{Status: "KO", Message: message})
if marshalErr != nil {
c.Logger.Error(marshalErr)
}
c.SetContentType("application/json")
c.SetBody(b)
c.Logger.Debug(err)
}
// ReplyUnauthorized response sent when user is unauthorized.
func (c *AutheliaCtx) ReplyUnauthorized() {
c.RequestCtx.Error(fasthttp.StatusMessage(fasthttp.StatusUnauthorized), fasthttp.StatusUnauthorized)
}
// ReplyForbidden response sent when access is forbidden to user.
func (c *AutheliaCtx) ReplyForbidden() {
c.RequestCtx.Error(fasthttp.StatusMessage(fasthttp.StatusForbidden), fasthttp.StatusForbidden)
}
// XForwardedProto return the content of the X-Forwarded-Proto header.
func (c *AutheliaCtx) XForwardedProto() []byte {
return c.RequestCtx.Request.Header.Peek(xForwardedProtoHeader)
}
// XForwardedMethod return the content of the X-Forwarded-Method header.
func (c *AutheliaCtx) XForwardedMethod() []byte {
return c.RequestCtx.Request.Header.Peek(xForwardedMethodHeader)
}
// XForwardedHost return the content of the X-Forwarded-Host header.
func (c *AutheliaCtx) XForwardedHost() []byte {
return c.RequestCtx.Request.Header.Peek(xForwardedHostHeader)
}
// XForwardedURI return the content of the X-Forwarded-URI header.
func (c *AutheliaCtx) XForwardedURI() []byte {
return c.RequestCtx.Request.Header.Peek(xForwardedURIHeader)
}
// XOriginalURL return the content of the X-Original-URL header.
func (c *AutheliaCtx) XOriginalURL() []byte {
return c.RequestCtx.Request.Header.Peek(xOriginalURLHeader)
}
// GetSession return the user session. Any update will be saved in cache.
func (c *AutheliaCtx) GetSession() session.UserSession {
userSession, err := c.Providers.SessionProvider.GetSession(c.RequestCtx)
if err != nil {
c.Logger.Error("Unable to retrieve user session")
return session.NewDefaultUserSession()
}
return userSession
}
// SaveSession save the content of the session.
func (c *AutheliaCtx) SaveSession(userSession session.UserSession) error {
return c.Providers.SessionProvider.SaveSession(c.RequestCtx, userSession)
}
// ReplyOK is a helper method to reply ok.
func (c *AutheliaCtx) ReplyOK() {
c.SetContentType(applicationJSONContentType)
c.SetBody(okMessageBytes)
}
// ParseBody parse the request body into the type of value.
func (c *AutheliaCtx) ParseBody(value interface{}) error {
err := json.Unmarshal(c.PostBody(), &value)
if err != nil {
return fmt.Errorf("Unable to parse body: %s", err)
}
valid, err := govalidator.ValidateStruct(value)
if err != nil {
return fmt.Errorf("Unable to validate body: %s", err)
}
if !valid {
return fmt.Errorf("Body is not valid")
}
return nil
}
// SetJSONBody Set json body.
func (c *AutheliaCtx) SetJSONBody(value interface{}) error {
b, err := json.Marshal(OKResponse{Status: "OK", Data: value})
if err != nil {
return fmt.Errorf("Unable to marshal JSON body")
}
c.SetContentType("application/json")
c.SetBody(b)
return nil
}
// RemoteIP return the remote IP taking X-Forwarded-For header into account if provided.
func (c *AutheliaCtx) RemoteIP() net.IP {
XForwardedFor := c.Request.Header.Peek("X-Forwarded-For")
if XForwardedFor != nil {
ips := strings.Split(string(XForwardedFor), ",")
if len(ips) > 0 {
return net.ParseIP(strings.Trim(ips[0], " "))
}
}
return c.RequestCtx.RemoteIP()
}