2019-04-24 21:52:08 +00:00
|
|
|
package middlewares
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"net"
|
2021-05-04 22:06:05 +00:00
|
|
|
"net/url"
|
2019-04-24 21:52:08 +00:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/asaskevich/govalidator"
|
2020-04-05 12:37:21 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"github.com/valyala/fasthttp"
|
|
|
|
|
2019-12-24 02:14:52 +00:00
|
|
|
"github.com/authelia/authelia/internal/configuration/schema"
|
|
|
|
"github.com/authelia/authelia/internal/session"
|
2020-01-17 22:48:48 +00:00
|
|
|
"github.com/authelia/authelia/internal/utils"
|
2019-04-24 21:52:08 +00:00
|
|
|
)
|
|
|
|
|
2019-12-11 07:52:02 +00:00
|
|
|
// 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(),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-04-24 21:52:08 +00:00
|
|
|
// 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
|
2019-12-11 07:52:02 +00:00
|
|
|
autheliaCtx.Logger = NewRequestLogger(autheliaCtx)
|
2020-01-17 22:48:48 +00:00
|
|
|
autheliaCtx.Clock = utils.RealClock{}
|
2020-05-05 19:35:32 +00:00
|
|
|
|
2019-04-24 21:52:08 +00:00
|
|
|
return autheliaCtx, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// AutheliaMiddleware is wrapping the RequestCtx into an AutheliaCtx providing Authelia related objects.
|
2021-05-04 22:06:05 +00:00
|
|
|
func AutheliaMiddleware(configuration schema.Configuration, providers Providers) RequestHandlerBridge {
|
2019-04-24 21:52:08 +00:00
|
|
|
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
|
|
|
|
}
|
2020-05-05 19:35:32 +00:00
|
|
|
|
2019-04-24 21:52:08 +00:00
|
|
|
next(autheliaCtx)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-16 19:50:58 +00:00
|
|
|
// Error reply with an error and display the stack trace in the logs.
|
2019-04-24 21:52:08 +00:00
|
|
|
func (c *AutheliaCtx) Error(err error, message string) {
|
2019-11-17 01:05:46 +00:00
|
|
|
b, marshalErr := json.Marshal(ErrorResponse{Status: "KO", Message: message})
|
|
|
|
|
|
|
|
if marshalErr != nil {
|
|
|
|
c.Logger.Error(marshalErr)
|
|
|
|
}
|
|
|
|
|
2019-04-24 21:52:08 +00:00
|
|
|
c.SetContentType("application/json")
|
|
|
|
c.SetBody(b)
|
|
|
|
c.Logger.Error(err)
|
|
|
|
}
|
|
|
|
|
2020-05-02 05:06:39 +00:00
|
|
|
// ReplyError reply with an error but does not display any stack trace in the logs.
|
2019-11-16 19:50:58 +00:00
|
|
|
func (c *AutheliaCtx) ReplyError(err error, message string) {
|
2019-11-17 01:05:46 +00:00
|
|
|
b, marshalErr := json.Marshal(ErrorResponse{Status: "KO", Message: message})
|
|
|
|
|
|
|
|
if marshalErr != nil {
|
|
|
|
c.Logger.Error(marshalErr)
|
|
|
|
}
|
|
|
|
|
2019-11-16 19:50:58 +00:00
|
|
|
c.SetContentType("application/json")
|
|
|
|
c.SetBody(b)
|
|
|
|
c.Logger.Debug(err)
|
|
|
|
}
|
|
|
|
|
2020-05-02 05:06:39 +00:00
|
|
|
// ReplyUnauthorized response sent when user is unauthorized.
|
2019-04-24 21:52:08 +00:00
|
|
|
func (c *AutheliaCtx) ReplyUnauthorized() {
|
|
|
|
c.RequestCtx.Error(fasthttp.StatusMessage(fasthttp.StatusUnauthorized), fasthttp.StatusUnauthorized)
|
|
|
|
}
|
|
|
|
|
2020-05-02 05:06:39 +00:00
|
|
|
// ReplyForbidden response sent when access is forbidden to user.
|
2019-04-24 21:52:08 +00:00
|
|
|
func (c *AutheliaCtx) ReplyForbidden() {
|
|
|
|
c.RequestCtx.Error(fasthttp.StatusMessage(fasthttp.StatusForbidden), fasthttp.StatusForbidden)
|
|
|
|
}
|
|
|
|
|
2021-05-04 22:06:05 +00:00
|
|
|
// ReplyBadRequest response sent when bad request has been sent.
|
|
|
|
func (c *AutheliaCtx) ReplyBadRequest() {
|
|
|
|
c.RequestCtx.Error(fasthttp.StatusMessage(fasthttp.StatusBadRequest), fasthttp.StatusBadRequest)
|
|
|
|
}
|
|
|
|
|
2021-03-05 04:18:31 +00:00
|
|
|
// XForwardedProto return the content of the X-Forwarded-Proto header.
|
2019-04-24 21:52:08 +00:00
|
|
|
func (c *AutheliaCtx) XForwardedProto() []byte {
|
|
|
|
return c.RequestCtx.Request.Header.Peek(xForwardedProtoHeader)
|
|
|
|
}
|
|
|
|
|
2021-03-05 04:18:31 +00:00
|
|
|
// 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.
|
2019-04-24 21:52:08 +00:00
|
|
|
func (c *AutheliaCtx) XForwardedHost() []byte {
|
|
|
|
return c.RequestCtx.Request.Header.Peek(xForwardedHostHeader)
|
|
|
|
}
|
|
|
|
|
2021-03-05 04:18:31 +00:00
|
|
|
// XForwardedURI return the content of the X-Forwarded-URI header.
|
2019-04-24 21:52:08 +00:00
|
|
|
func (c *AutheliaCtx) XForwardedURI() []byte {
|
|
|
|
return c.RequestCtx.Request.Header.Peek(xForwardedURIHeader)
|
|
|
|
}
|
|
|
|
|
2021-05-04 22:06:05 +00:00
|
|
|
// ForwardedProtoHost gets the X-Forwarded-Proto and X-Forwarded-Host headers and forms them into a URL.
|
|
|
|
func (c AutheliaCtx) ForwardedProtoHost() (string, error) {
|
|
|
|
XForwardedProto := c.XForwardedProto()
|
|
|
|
|
|
|
|
if XForwardedProto == nil {
|
|
|
|
return "", errMissingXForwardedProto
|
|
|
|
}
|
|
|
|
|
|
|
|
XForwardedHost := c.XForwardedHost()
|
|
|
|
|
|
|
|
if XForwardedHost == nil {
|
|
|
|
return "", errMissingXForwardedHost
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf("%s://%s", XForwardedProto,
|
|
|
|
XForwardedHost), nil
|
|
|
|
}
|
|
|
|
|
2021-03-05 04:18:31 +00:00
|
|
|
// XOriginalURL return the content of the X-Original-URL header.
|
2019-04-24 21:52:08 +00:00
|
|
|
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 {
|
2020-01-17 22:48:48 +00:00
|
|
|
userSession, err := c.Providers.SessionProvider.GetSession(c.RequestCtx)
|
|
|
|
if err != nil {
|
|
|
|
c.Logger.Error("Unable to retrieve user session")
|
|
|
|
return session.NewDefaultUserSession()
|
|
|
|
}
|
2020-05-05 19:35:32 +00:00
|
|
|
|
2020-01-17 22:48:48 +00:00
|
|
|
return userSession
|
2019-04-24 21:52:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// SaveSession save the content of the session.
|
|
|
|
func (c *AutheliaCtx) SaveSession(userSession session.UserSession) error {
|
|
|
|
return c.Providers.SessionProvider.SaveSession(c.RequestCtx, userSession)
|
|
|
|
}
|
|
|
|
|
2020-05-02 05:06:39 +00:00
|
|
|
// ReplyOK is a helper method to reply ok.
|
2019-04-24 21:52:08 +00:00
|
|
|
func (c *AutheliaCtx) ReplyOK() {
|
|
|
|
c.SetContentType(applicationJSONContentType)
|
|
|
|
c.SetBody(okMessageBytes)
|
|
|
|
}
|
|
|
|
|
2020-05-02 05:06:39 +00:00
|
|
|
// ParseBody parse the request body into the type of value.
|
2019-04-24 21:52:08 +00:00
|
|
|
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")
|
|
|
|
}
|
2020-05-05 19:35:32 +00:00
|
|
|
|
2019-04-24 21:52:08 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-05-02 05:06:39 +00:00
|
|
|
// SetJSONBody Set json body.
|
2019-04-24 21:52:08 +00:00
|
|
|
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)
|
2020-05-05 19:35:32 +00:00
|
|
|
|
2019-04-24 21:52:08 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoteIP return the remote IP taking X-Forwarded-For header into account if provided.
|
|
|
|
func (c *AutheliaCtx) RemoteIP() net.IP {
|
2019-12-11 07:52:02 +00:00
|
|
|
XForwardedFor := c.Request.Header.Peek("X-Forwarded-For")
|
2019-04-24 21:52:08 +00:00
|
|
|
if XForwardedFor != nil {
|
|
|
|
ips := strings.Split(string(XForwardedFor), ",")
|
|
|
|
|
|
|
|
if len(ips) > 0 {
|
2019-12-11 07:29:32 +00:00
|
|
|
return net.ParseIP(strings.Trim(ips[0], " "))
|
2019-04-24 21:52:08 +00:00
|
|
|
}
|
|
|
|
}
|
2020-05-05 19:35:32 +00:00
|
|
|
|
2019-04-24 21:52:08 +00:00
|
|
|
return c.RequestCtx.RemoteIP()
|
|
|
|
}
|
2021-05-04 22:06:05 +00:00
|
|
|
|
|
|
|
// GetOriginalURL extract the URL from the request headers (X-Original-URI or X-Forwarded-* headers).
|
|
|
|
func (c *AutheliaCtx) GetOriginalURL() (*url.URL, error) {
|
|
|
|
originalURL := c.XOriginalURL()
|
|
|
|
if originalURL != nil {
|
|
|
|
parsedURL, err := url.ParseRequestURI(string(originalURL))
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Unable to parse URL extracted from X-Original-URL header: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Logger.Trace("Using X-Original-URL header content as targeted site URL")
|
|
|
|
|
|
|
|
return parsedURL, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
forwardedProto := c.XForwardedProto()
|
|
|
|
forwardedHost := c.XForwardedHost()
|
|
|
|
forwardedURI := c.XForwardedURI()
|
|
|
|
|
|
|
|
if forwardedProto == nil {
|
|
|
|
return nil, errMissingXForwardedProto
|
|
|
|
}
|
|
|
|
|
|
|
|
if forwardedHost == nil {
|
|
|
|
return nil, errMissingXForwardedHost
|
|
|
|
}
|
|
|
|
|
|
|
|
var requestURI string
|
|
|
|
|
2021-06-11 00:30:53 +00:00
|
|
|
scheme := forwardedProto
|
|
|
|
scheme = append(scheme, protoHostSeparator...)
|
2021-05-04 22:06:05 +00:00
|
|
|
requestURI = string(append(scheme,
|
|
|
|
append(forwardedHost, forwardedURI...)...))
|
|
|
|
|
|
|
|
parsedURL, err := url.ParseRequestURI(requestURI)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Unable to parse URL %s: %v", requestURI, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Logger.Tracef("Using X-Fowarded-Proto, X-Forwarded-Host and X-Forwarded-URI headers " +
|
|
|
|
"to construct targeted site URL")
|
|
|
|
|
|
|
|
return parsedURL, nil
|
|
|
|
}
|