authelia/internal/handlers/handler_authz.go

175 lines
4.8 KiB
Go

package handlers
import (
"fmt"
"net/url"
"github.com/authelia/authelia/v4/internal/authentication"
"github.com/authelia/authelia/v4/internal/authorization"
"github.com/authelia/authelia/v4/internal/middlewares"
"github.com/authelia/authelia/v4/internal/session"
"github.com/authelia/authelia/v4/internal/utils"
)
// Handler is the middlewares.RequestHandler for Authz.
func (authz *Authz) Handler(ctx *middlewares.AutheliaCtx) {
var (
object authorization.Object
autheliaURL *url.URL
provider *session.Session
err error
)
if object, err = authz.handleGetObject(ctx); err != nil {
ctx.Logger.WithError(err).Error("Error getting Target URL and Request Method")
ctx.ReplyStatusCode(authz.config.StatusCodeBadRequest)
return
}
if !utils.IsURISecure(object.URL) {
ctx.Logger.Errorf("Target URL '%s' has an insecure scheme '%s', only the 'https' and 'wss' schemes are supported so session cookies can be transmitted securely", object.URL.String(), object.URL.Scheme)
ctx.ReplyStatusCode(authz.config.StatusCodeBadRequest)
return
}
if provider, err = ctx.GetSessionProviderByTargetURL(object.URL); err != nil {
ctx.Logger.WithError(err).WithField("target_url", object.URL.String()).Error("Target URL does not appear to have a relevant session cookies configuration")
ctx.ReplyStatusCode(authz.config.StatusCodeBadRequest)
return
}
if autheliaURL, err = authz.getAutheliaURL(ctx, provider); err != nil {
ctx.Logger.WithError(err).WithField("target_url", object.URL.String()).Error("Error occurred trying to determine the external Authelia URL for Target URL")
ctx.ReplyStatusCode(authz.config.StatusCodeBadRequest)
return
}
var (
authn Authn
strategy AuthnStrategy
)
if authn, strategy, err = authz.authn(ctx, provider); err != nil {
authn.Object = object
ctx.Logger.WithError(err).Error("Error occurred while attempting to authenticate a request")
switch strategy {
case nil:
ctx.ReplyUnauthorized()
default:
strategy.HandleUnauthorized(ctx, &authn, authz.getRedirectionURL(&object, autheliaURL))
}
return
}
authn.Object = object
authn.Method = friendlyMethod(authn.Object.Method)
ruleHasSubject, required := ctx.Providers.Authorizer.GetRequiredLevel(
authorization.Subject{
Username: authn.Details.Username,
Groups: authn.Details.Groups,
IP: ctx.RemoteIP(),
},
object,
)
switch isAuthzResult(authn.Level, required, ruleHasSubject) {
case AuthzResultForbidden:
ctx.Logger.Infof("Access to '%s' is forbidden to user '%s'", object.URL.String(), authn.Username)
ctx.ReplyForbidden()
case AuthzResultUnauthorized:
var handler HandlerAuthzUnauthorized
if strategy != nil {
handler = strategy.HandleUnauthorized
} else {
handler = authz.handleUnauthorized
}
handler(ctx, &authn, authz.getRedirectionURL(&object, autheliaURL))
case AuthzResultAuthorized:
authz.handleAuthorized(ctx, &authn)
}
}
func (authz *Authz) getAutheliaURL(ctx *middlewares.AutheliaCtx, provider *session.Session) (autheliaURL *url.URL, err error) {
if autheliaURL, err = authz.handleGetAutheliaURL(ctx); err != nil {
return nil, err
}
switch {
case authz.implementation == AuthzImplLegacy:
return autheliaURL, nil
case autheliaURL != nil:
switch {
case utils.HasURIDomainSuffix(autheliaURL, provider.Config.Domain):
return autheliaURL, nil
default:
return nil, fmt.Errorf("authelia url '%s' is not valid for detected domain '%s' as the url does not have the domain as a suffix", autheliaURL.String(), provider.Config.Domain)
}
}
if provider.Config.AutheliaURL != nil {
return provider.Config.AutheliaURL, nil
}
return nil, fmt.Errorf("authelia url lookup failed")
}
func (authz *Authz) getRedirectionURL(object *authorization.Object, autheliaURL *url.URL) (redirectionURL *url.URL) {
if autheliaURL == nil {
return nil
}
redirectionURL, _ = url.ParseRequestURI(autheliaURL.String())
if redirectionURL.Path == "" {
redirectionURL.Path = "/"
}
qry := redirectionURL.Query()
qry.Set(queryArgRD, object.URL.String())
if object.Method != "" {
qry.Set(queryArgRM, object.Method)
}
redirectionURL.RawQuery = qry.Encode()
return redirectionURL
}
func (authz *Authz) authn(ctx *middlewares.AutheliaCtx, provider *session.Session) (authn Authn, strategy AuthnStrategy, err error) {
for _, strategy = range authz.strategies {
if authn, err = strategy.Get(ctx, provider); err != nil {
if strategy.CanHandleUnauthorized() {
return Authn{Type: authn.Type, Level: authentication.NotAuthenticated, Username: anonymous}, strategy, err
}
return Authn{Type: authn.Type, Level: authentication.NotAuthenticated, Username: anonymous}, nil, err
}
if authn.Level != authentication.NotAuthenticated {
break
}
}
if strategy.CanHandleUnauthorized() {
return authn, strategy, err
}
return authn, nil, nil
}