2020-02-01 12:54:50 +00:00
package handlers
import (
"fmt"
"net/url"
2022-10-20 02:16:36 +00:00
"path"
2021-11-29 03:09:14 +00:00
"time"
2020-02-01 12:54:50 +00:00
2022-10-20 02:16:36 +00:00
"github.com/google/uuid"
2020-05-05 21:27:38 +00:00
"github.com/valyala/fasthttp"
2021-08-11 01:04:35 +00:00
"github.com/authelia/authelia/v4/internal/authorization"
"github.com/authelia/authelia/v4/internal/middlewares"
2022-10-20 02:16:36 +00:00
"github.com/authelia/authelia/v4/internal/model"
2022-04-07 05:33:53 +00:00
"github.com/authelia/authelia/v4/internal/oidc"
2021-08-11 01:04:35 +00:00
"github.com/authelia/authelia/v4/internal/utils"
2020-02-01 12:54:50 +00:00
)
2020-05-02 05:06:39 +00:00
// Handle1FAResponse handle the redirection upon 1FA authentication.
2021-03-05 04:18:31 +00:00
func Handle1FAResponse ( ctx * middlewares . AutheliaCtx , targetURI , requestMethod string , username string , groups [ ] string ) {
2022-07-26 05:43:39 +00:00
var err error
if len ( targetURI ) == 0 {
2020-03-06 00:31:09 +00:00
if ! ctx . Providers . Authorizer . IsSecondFactorEnabled ( ) && ctx . Configuration . DefaultRedirectionURL != "" {
2022-07-26 05:43:39 +00:00
if err = ctx . SetJSONBody ( redirectResponse { Redirect : ctx . Configuration . DefaultRedirectionURL } ) ; err != nil {
2020-12-16 01:47:31 +00:00
ctx . Logger . Errorf ( "Unable to set default redirection URL in body: %s" , err )
}
2020-02-04 21:18:02 +00:00
} else {
ctx . ReplyOK ( )
2020-02-01 12:54:50 +00:00
}
2020-05-05 19:35:32 +00:00
2020-02-04 21:18:02 +00:00
return
}
2022-07-26 05:43:39 +00:00
var targetURL * url . URL
if targetURL , err = url . ParseRequestURI ( targetURI ) ; err != nil {
2021-09-17 05:53:40 +00:00
ctx . Error ( fmt . Errorf ( "unable to parse target URL %s: %s" , targetURI , err ) , messageAuthenticationFailed )
2022-07-26 05:43:39 +00:00
2020-02-04 21:18:02 +00:00
return
}
2022-09-04 22:21:30 +00:00
_ , requiredLevel := ctx . Providers . Authorizer . GetRequiredLevel (
2021-03-05 04:18:31 +00:00
authorization . Subject {
Username : username ,
Groups : groups ,
IP : ctx . RemoteIP ( ) ,
} ,
authorization . NewObject ( targetURL , requestMethod ) )
2020-02-04 21:18:02 +00:00
ctx . Logger . Debugf ( "Required level for the URL %s is %d" , targetURI , requiredLevel )
2020-04-19 11:45:46 +00:00
if requiredLevel == authorization . TwoFactor {
ctx . Logger . Warnf ( "%s requires 2FA, cannot be redirected yet" , targetURI )
2020-02-04 21:18:02 +00:00
ctx . ReplyOK ( )
2020-05-05 19:35:32 +00:00
2020-02-04 21:18:02 +00:00
return
}
2022-09-03 01:51:02 +00:00
if ! utils . IsURISafeRedirection ( targetURL , ctx . Configuration . Session . Domain ) {
2021-09-17 05:53:40 +00:00
ctx . Logger . Debugf ( "Redirection URL %s is not safe" , targetURI )
2020-03-06 00:31:09 +00:00
if ! ctx . Providers . Authorizer . IsSecondFactorEnabled ( ) && ctx . Configuration . DefaultRedirectionURL != "" {
2022-07-26 05:43:39 +00:00
if err = ctx . SetJSONBody ( redirectResponse { Redirect : ctx . Configuration . DefaultRedirectionURL } ) ; err != nil {
2020-12-16 01:47:31 +00:00
ctx . Logger . Errorf ( "Unable to set default redirection URL in body: %s" , err )
}
2022-07-26 05:43:39 +00:00
return
2020-02-01 12:54:50 +00:00
}
2020-05-05 19:35:32 +00:00
2022-07-26 05:43:39 +00:00
ctx . ReplyOK ( )
2020-02-04 21:18:02 +00:00
return
}
ctx . Logger . Debugf ( "Redirection URL %s is safe" , targetURI )
2020-05-05 19:35:32 +00:00
2022-07-26 05:43:39 +00:00
if err = ctx . SetJSONBody ( redirectResponse { Redirect : targetURI } ) ; err != nil {
2020-12-16 01:47:31 +00:00
ctx . Logger . Errorf ( "Unable to set redirection URL in body: %s" , err )
}
2020-02-04 21:18:02 +00:00
}
2020-05-02 05:06:39 +00:00
// Handle2FAResponse handle the redirection upon 2FA authentication.
2020-02-04 21:18:02 +00:00
func Handle2FAResponse ( ctx * middlewares . AutheliaCtx , targetURI string ) {
2022-07-26 05:43:39 +00:00
var err error
if len ( targetURI ) == 0 {
if len ( ctx . Configuration . DefaultRedirectionURL ) == 0 {
2020-02-01 12:54:50 +00:00
ctx . ReplyOK ( )
2022-07-26 05:43:39 +00:00
return
}
if err = ctx . SetJSONBody ( redirectResponse { Redirect : ctx . Configuration . DefaultRedirectionURL } ) ; err != nil {
ctx . Logger . Errorf ( "Unable to set default redirection URL in body: %s" , err )
2020-02-01 12:54:50 +00:00
}
2020-05-05 19:35:32 +00:00
2020-02-04 21:18:02 +00:00
return
}
2022-07-26 05:43:39 +00:00
var safe bool
2020-02-04 21:18:02 +00:00
2022-09-03 01:51:02 +00:00
if safe , err = utils . IsURIStringSafeRedirection ( targetURI , ctx . Configuration . Session . Domain ) ; err != nil {
2021-09-17 05:53:40 +00:00
ctx . Error ( fmt . Errorf ( "unable to check target URL: %s" , err ) , messageMFAValidationFailed )
2022-07-26 05:43:39 +00:00
2020-02-04 21:18:02 +00:00
return
}
2021-08-02 06:15:38 +00:00
if safe {
ctx . Logger . Debugf ( "Redirection URL %s is safe" , targetURI )
2022-07-26 05:43:39 +00:00
if err = ctx . SetJSONBody ( redirectResponse { Redirect : targetURI } ) ; err != nil {
2020-12-16 01:47:31 +00:00
ctx . Logger . Errorf ( "Unable to set redirection URL in body: %s" , err )
}
2022-07-26 05:43:39 +00:00
return
2020-02-01 12:54:50 +00:00
}
2022-07-26 05:43:39 +00:00
ctx . ReplyOK ( )
2020-02-01 12:54:50 +00:00
}
2020-05-05 21:27:38 +00:00
2022-10-20 02:16:36 +00:00
// handleOIDCWorkflowResponse handle the redirection upon authentication in the OIDC workflow.
func handleOIDCWorkflowResponse ( ctx * middlewares . AutheliaCtx , targetURI , workflowID string ) {
switch {
case len ( workflowID ) != 0 :
handleOIDCWorkflowResponseWithID ( ctx , workflowID )
case len ( targetURI ) != 0 :
handleOIDCWorkflowResponseWithTargetURL ( ctx , targetURI )
default :
ctx . Error ( fmt . Errorf ( "invalid post data: must contain either a target url or a workflow id" ) , messageAuthenticationFailed )
}
}
func handleOIDCWorkflowResponseWithTargetURL ( ctx * middlewares . AutheliaCtx , targetURI string ) {
var (
issuerURL * url . URL
targetURL * url . URL
err error
)
if targetURL , err = url . ParseRequestURI ( targetURI ) ; err != nil {
ctx . Error ( fmt . Errorf ( "unable to parse target URL '%s': %w" , targetURI , err ) , messageAuthenticationFailed )
return
}
if issuerURL , err = ctx . IssuerURL ( ) ; err != nil {
ctx . Error ( fmt . Errorf ( "unable to get issuer for redirection: %w" , err ) , messageAuthenticationFailed )
return
}
if targetURL . Host != issuerURL . Host {
ctx . Error ( fmt . Errorf ( "unable to redirect to '%s': target host '%s' does not match expected issuer host '%s'" , targetURL , targetURL . Host , issuerURL . Host ) , messageAuthenticationFailed )
return
}
userSession := ctx . GetSession ( )
if userSession . IsAnonymous ( ) {
ctx . Error ( fmt . Errorf ( "unable to redirect to '%s': user is anonymous" , targetURL ) , messageAuthenticationFailed )
return
}
if err = ctx . SetJSONBody ( redirectResponse { Redirect : targetURL . String ( ) } ) ; err != nil {
ctx . Logger . Errorf ( "Unable to set default redirection URL in body: %s" , err )
}
}
func handleOIDCWorkflowResponseWithID ( ctx * middlewares . AutheliaCtx , id string ) {
var (
workflowID uuid . UUID
client * oidc . Client
consent * model . OAuth2ConsentSession
err error
)
if workflowID , err = uuid . Parse ( id ) ; err != nil {
ctx . Error ( fmt . Errorf ( "unable to parse consent session challenge id '%s': %w" , id , err ) , messageAuthenticationFailed )
return
}
if consent , err = ctx . Providers . StorageProvider . LoadOAuth2ConsentSessionByChallengeID ( ctx , workflowID ) ; err != nil {
ctx . Error ( fmt . Errorf ( "unable to load consent session by challenge id '%s': %w" , id , err ) , messageAuthenticationFailed )
return
}
if consent . Responded ( ) {
ctx . Error ( fmt . Errorf ( "consent has already been responded to '%s': %w" , id , err ) , messageAuthenticationFailed )
return
}
if client , err = ctx . Providers . OpenIDConnect . GetFullClient ( consent . ClientID ) ; err != nil {
ctx . Error ( fmt . Errorf ( "unable to get client for client with id '%s' with consent challenge id '%s': %w" , id , consent . ChallengeID , err ) , messageAuthenticationFailed )
return
}
userSession := ctx . GetSession ( )
if userSession . IsAnonymous ( ) {
ctx . Error ( fmt . Errorf ( "unable to redirect for authorization/consent for client with id '%s' with consent challenge id '%s': user is anonymous" , client . ID , consent . ChallengeID ) , messageAuthenticationFailed )
return
}
if ! client . IsAuthenticationLevelSufficient ( userSession . AuthenticationLevel ) {
ctx . Logger . Warnf ( "OpenID Connect client '%s' requires 2FA, cannot be redirected yet" , client . ID )
ctx . ReplyOK ( )
return
}
var (
targetURL * url . URL
form url . Values
)
if targetURL , err = ctx . IssuerURL ( ) ; err != nil {
ctx . Error ( fmt . Errorf ( "unable to get issuer for redirection: %w" , err ) , messageAuthenticationFailed )
return
}
if form , err = consent . GetForm ( ) ; err != nil {
ctx . Error ( fmt . Errorf ( "unable to get authorization form values from consent session with challenge id '%s': %w" , consent . ChallengeID , err ) , messageAuthenticationFailed )
return
}
form . Set ( queryArgConsentID , workflowID . String ( ) )
targetURL . Path = path . Join ( targetURL . Path , oidc . EndpointPathAuthorization )
targetURL . RawQuery = form . Encode ( )
if err = ctx . SetJSONBody ( redirectResponse { Redirect : targetURL . String ( ) } ) ; err != nil {
ctx . Logger . Errorf ( "Unable to set default redirection URL in body: %s" , err )
}
}
2021-11-29 03:09:14 +00:00
func markAuthenticationAttempt ( ctx * middlewares . AutheliaCtx , successful bool , bannedUntil * time . Time , username string , authType string , errAuth error ) ( err error ) {
// We only Mark if there was no underlying error.
ctx . Logger . Debugf ( "Mark %s authentication attempt made by user '%s'" , authType , username )
2021-12-02 02:21:46 +00:00
var (
requestURI , requestMethod string
)
referer := ctx . Request . Header . Referer ( )
if referer != nil {
2022-09-03 01:51:02 +00:00
refererURL , err := url . ParseRequestURI ( string ( referer ) )
2021-12-02 02:21:46 +00:00
if err == nil {
2022-10-20 02:16:36 +00:00
requestURI = refererURL . Query ( ) . Get ( queryArgRD )
2021-12-02 02:21:46 +00:00
requestMethod = refererURL . Query ( ) . Get ( "rm" )
}
}
2022-06-14 07:20:13 +00:00
if err = ctx . Providers . Regulator . Mark ( ctx , successful , bannedUntil != nil , username , requestURI , requestMethod , authType ) ; err != nil {
2021-11-29 03:09:14 +00:00
ctx . Logger . Errorf ( "Unable to mark %s authentication attempt by user '%s': %+v" , authType , username , err )
return err
}
if successful {
ctx . Logger . Debugf ( "Successful %s authentication attempt made by user '%s'" , authType , username )
} else {
switch {
case errAuth != nil :
ctx . Logger . Errorf ( "Unsuccessful %s authentication attempt by user '%s': %+v" , authType , username , errAuth )
case bannedUntil != nil :
ctx . Logger . Errorf ( "Unsuccessful %s authentication attempt by user '%s' and they are banned until %s" , authType , username , bannedUntil )
default :
ctx . Logger . Errorf ( "Unsuccessful %s authentication attempt by user '%s'" , authType , username )
}
}
return nil
}
func respondUnauthorized ( ctx * middlewares . AutheliaCtx , message string ) {
2020-05-05 21:27:38 +00:00
ctx . SetStatusCode ( fasthttp . StatusUnauthorized )
2021-11-29 03:09:14 +00:00
ctx . SetJSONError ( message )
2020-05-05 21:27:38 +00:00
}
2022-04-03 23:58:01 +00:00
// SetStatusCodeResponse writes a response status code and an appropriate body on either a
// *fasthttp.RequestCtx or *middlewares.AutheliaCtx.
2022-06-14 07:20:13 +00:00
func SetStatusCodeResponse ( ctx * fasthttp . RequestCtx , statusCode int ) {
ctx . Response . Reset ( )
2022-07-11 06:24:09 +00:00
middlewares . SetContentTypeTextPlain ( ctx )
2022-04-03 23:58:01 +00:00
ctx . SetStatusCode ( statusCode )
ctx . SetBodyString ( fmt . Sprintf ( "%d %s" , statusCode , fasthttp . StatusMessage ( statusCode ) ) )
}