2019-04-24 21:52:08 +00:00
package handlers
import (
2021-02-23 23:35:04 +00:00
"bytes"
2019-04-24 21:52:08 +00:00
"encoding/base64"
"fmt"
"net"
"net/url"
"strings"
2020-05-04 19:39:25 +00:00
"time"
2019-04-24 21:52:08 +00:00
2020-04-05 12:37:21 +00:00
"github.com/valyala/fasthttp"
2019-12-24 02:14:52 +00:00
"github.com/authelia/authelia/internal/authentication"
"github.com/authelia/authelia/internal/authorization"
2020-05-04 19:39:25 +00:00
"github.com/authelia/authelia/internal/configuration/schema"
2019-12-24 02:14:52 +00:00
"github.com/authelia/authelia/internal/middlewares"
2020-05-04 19:39:25 +00:00
"github.com/authelia/authelia/internal/session"
"github.com/authelia/authelia/internal/utils"
2019-04-24 21:52:08 +00:00
)
2020-02-18 22:39:07 +00:00
func isURLUnderProtectedDomain ( url * url . URL , domain string ) bool {
return strings . HasSuffix ( url . Hostname ( ) , domain )
}
func isSchemeHTTPS ( url * url . URL ) bool {
return url . Scheme == "https"
}
2020-02-27 23:28:53 +00:00
func isSchemeWSS ( url * url . URL ) bool {
return url . Scheme == "wss"
}
2020-05-02 05:06:39 +00:00
// getOriginalURL extract the URL from the request headers (X-Original-URI or X-Forwarded-* headers).
2019-04-24 21:52:08 +00:00
func getOriginalURL ( ctx * middlewares . AutheliaCtx ) ( * url . URL , error ) {
originalURL := ctx . XOriginalURL ( )
if originalURL != nil {
url , err := url . ParseRequestURI ( string ( originalURL ) )
if err != nil {
2020-02-06 02:24:25 +00:00
return nil , fmt . Errorf ( "Unable to parse URL extracted from X-Original-URL header: %v" , err )
2019-04-24 21:52:08 +00:00
}
2020-05-05 19:35:32 +00:00
2020-05-04 19:39:25 +00:00
ctx . Logger . Trace ( "Using X-Original-URL header content as targeted site URL" )
2020-05-05 19:35:32 +00:00
2019-04-24 21:52:08 +00:00
return url , nil
}
forwardedProto := ctx . XForwardedProto ( )
forwardedHost := ctx . XForwardedHost ( )
forwardedURI := ctx . XForwardedURI ( )
2020-02-06 02:24:25 +00:00
if forwardedProto == nil {
return nil , errMissingXForwardedProto
2019-04-24 21:52:08 +00:00
}
2020-02-06 02:24:25 +00:00
if forwardedHost == nil {
return nil , errMissingXForwardedHost
2019-04-24 21:52:08 +00:00
}
2020-02-06 02:24:25 +00:00
var requestURI string
2020-05-05 19:35:32 +00:00
2020-02-06 02:24:25 +00:00
scheme := append ( forwardedProto , protoHostSeparator ... )
2019-04-24 21:52:08 +00:00
requestURI = string ( append ( scheme ,
append ( forwardedHost , forwardedURI ... ) ... ) )
2020-02-06 02:24:25 +00:00
url , err := url . ParseRequestURI ( requestURI )
2019-04-24 21:52:08 +00:00
if err != nil {
2020-02-06 02:24:25 +00:00
return nil , fmt . Errorf ( "Unable to parse URL %s: %v" , requestURI , err )
2019-04-24 21:52:08 +00:00
}
2020-05-05 19:35:32 +00:00
2020-08-21 01:15:20 +00:00
ctx . Logger . Tracef ( "Using X-Forwarded-Proto, X-Forwarded-Host and X-Forwarded-URI headers " +
2020-03-22 22:12:24 +00:00
"to construct targeted site URL" )
2020-05-05 19:35:32 +00:00
2019-04-24 21:52:08 +00:00
return url , nil
}
2020-05-02 05:06:39 +00:00
// parseBasicAuth parses an HTTP Basic Authentication string.
// "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true).
2021-02-23 23:35:04 +00:00
func parseBasicAuth ( header , auth string ) ( username , password string , err error ) {
2019-04-24 21:52:08 +00:00
if ! strings . HasPrefix ( auth , authPrefix ) {
2021-02-23 23:35:04 +00:00
return "" , "" , fmt . Errorf ( "%s prefix not found in %s header" , strings . Trim ( authPrefix , " " ) , header )
2019-04-24 21:52:08 +00:00
}
2020-05-05 19:35:32 +00:00
2019-04-24 21:52:08 +00:00
c , err := base64 . StdEncoding . DecodeString ( auth [ len ( authPrefix ) : ] )
if err != nil {
return "" , "" , err
}
2020-05-05 19:35:32 +00:00
2019-04-24 21:52:08 +00:00
cs := string ( c )
s := strings . IndexByte ( cs , ':' )
2020-05-05 19:35:32 +00:00
2019-04-24 21:52:08 +00:00
if s < 0 {
2021-02-23 23:35:04 +00:00
return "" , "" , fmt . Errorf ( "Format of %s header must be user:password" , header )
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 cs [ : s ] , cs [ s + 1 : ] , nil
}
2020-05-02 05:06:39 +00:00
// isTargetURLAuthorized check whether the given user is authorized to access the resource.
2019-04-24 21:52:08 +00:00
func isTargetURLAuthorized ( authorizer * authorization . Authorizer , targetURL url . URL ,
2021-03-05 04:18:31 +00:00
username string , userGroups [ ] string , clientIP net . IP , method [ ] byte , authLevel authentication . Level ) authorizationMatching {
level := authorizer . GetRequiredLevel (
authorization . Subject {
Username : username ,
Groups : userGroups ,
IP : clientIP ,
} ,
authorization . NewObjectRaw ( & targetURL , method ) )
2019-04-24 21:52:08 +00:00
2020-05-05 21:27:38 +00:00
switch {
case level == authorization . Bypass :
2019-04-24 21:52:08 +00:00
return Authorized
2020-05-05 21:27:38 +00:00
case level == authorization . Denied && username != "" :
2019-04-24 21:52:08 +00:00
// If the user is not anonymous, it means that we went through
// all the rules related to that user and knowing who he is we can
2020-04-09 01:05:17 +00:00
// deduce the access is forbidden
2019-04-24 21:52:08 +00:00
// For anonymous users though, we cannot be sure that she
// could not be granted the rights to access the resource. Consequently
2020-04-09 01:05:17 +00:00
// for anonymous users we send Unauthorized instead of Forbidden
2019-04-24 21:52:08 +00:00
return Forbidden
2020-05-05 21:27:38 +00:00
case level == authorization . OneFactor && authLevel >= authentication . OneFactor ,
level == authorization . TwoFactor && authLevel >= authentication . TwoFactor :
return Authorized
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 NotAuthorized
}
// verifyBasicAuth verify that the provided username and password are correct and
2020-05-02 05:06:39 +00:00
// that the user is authorized to target the resource.
2021-02-23 23:35:04 +00:00
func verifyBasicAuth ( header string , auth [ ] byte , targetURL url . URL , ctx * middlewares . AutheliaCtx ) ( username , name string , groups , emails [ ] string , authLevel authentication . Level , err error ) { //nolint:unparam
username , password , err := parseBasicAuth ( header , string ( auth ) )
2019-04-24 21:52:08 +00:00
if err != nil {
2021-02-23 23:35:04 +00:00
return "" , "" , nil , nil , authentication . NotAuthenticated , fmt . Errorf ( "Unable to parse content of %s header: %s" , header , err )
2019-04-24 21:52:08 +00:00
}
authenticated , err := ctx . Providers . UserProvider . CheckUserPassword ( username , password )
if err != nil {
2021-02-23 23:35:04 +00:00
return "" , "" , nil , nil , authentication . NotAuthenticated , fmt . Errorf ( "Unable to check credentials extracted from %s header: %s" , header , err )
2019-04-24 21:52:08 +00:00
}
2020-05-02 05:06:39 +00:00
// If the user is not correctly authenticated, send a 401.
2019-04-24 21:52:08 +00:00
if ! authenticated {
// Request Basic Authentication otherwise
2020-10-26 11:38:08 +00:00
return "" , "" , nil , nil , authentication . NotAuthenticated , fmt . Errorf ( "User %s is not authenticated" , username )
2019-04-24 21:52:08 +00:00
}
details , err := ctx . Providers . UserProvider . GetDetails ( username )
if err != nil {
2020-10-26 11:38:08 +00:00
return "" , "" , nil , nil , authentication . NotAuthenticated , fmt . Errorf ( "Unable to retrieve details of user %s: %s" , username , err )
2019-04-24 21:52:08 +00:00
}
2020-10-26 11:38:08 +00:00
return username , details . DisplayName , details . Groups , details . Emails , authentication . OneFactor , nil
2019-04-24 21:52:08 +00:00
}
2020-10-26 11:38:08 +00:00
// setForwardedHeaders set the forwarded User, Groups, Name and Email headers.
func setForwardedHeaders ( headers * fasthttp . ResponseHeader , username , name string , groups , emails [ ] string ) {
2019-04-24 21:52:08 +00:00
if username != "" {
headers . Set ( remoteUserHeader , username )
headers . Set ( remoteGroupsHeader , strings . Join ( groups , "," ) )
2020-10-26 11:38:08 +00:00
headers . Set ( remoteNameHeader , name )
2020-11-16 11:22:16 +00:00
if emails != nil {
headers . Set ( remoteEmailHeader , emails [ 0 ] )
} else {
headers . Set ( remoteEmailHeader , "" )
}
2019-04-24 21:52:08 +00:00
}
}
2020-05-04 19:39:25 +00:00
// hasUserBeenInactiveTooLong checks whether the user has been inactive for too long.
func hasUserBeenInactiveTooLong ( ctx * middlewares . AutheliaCtx ) ( bool , error ) { //nolint:unparam
2020-04-05 12:37:21 +00:00
maxInactivityPeriod := int64 ( ctx . Providers . SessionProvider . Inactivity . Seconds ( ) )
2019-04-24 21:52:08 +00:00
if maxInactivityPeriod == 0 {
return false , nil
}
lastActivity := ctx . GetSession ( ) . LastActivity
2020-01-17 22:48:48 +00:00
inactivityPeriod := ctx . Clock . Now ( ) . Unix ( ) - lastActivity
2019-04-24 21:52:08 +00:00
2019-11-16 19:50:58 +00:00
ctx . Logger . Tracef ( "Inactivity report: Inactivity=%d, MaxInactivity=%d" ,
2019-04-24 21:52:08 +00:00
inactivityPeriod , maxInactivityPeriod )
if inactivityPeriod > maxInactivityPeriod {
return true , nil
}
return false , nil
}
2020-05-04 19:39:25 +00:00
// verifySessionCookie verifies if a user is identified by a cookie.
2020-05-05 21:27:38 +00:00
func verifySessionCookie ( ctx * middlewares . AutheliaCtx , targetURL * url . URL , userSession * session . UserSession , refreshProfile bool ,
2020-10-26 11:38:08 +00:00
refreshProfileInterval time . Duration ) ( username , name string , groups , emails [ ] string , authLevel authentication . Level , err error ) {
2020-05-02 05:06:39 +00:00
// No username in the session means the user is anonymous.
2019-04-24 21:52:08 +00:00
isUserAnonymous := userSession . Username == ""
if isUserAnonymous && userSession . AuthenticationLevel != authentication . NotAuthenticated {
2020-10-26 11:38:08 +00:00
return "" , "" , nil , nil , authentication . NotAuthenticated , fmt . Errorf ( "An anonymous user cannot be authenticated. That might be the sign of a compromise" )
2019-04-24 21:52:08 +00:00
}
2020-01-17 22:48:48 +00:00
if ! userSession . KeepMeLoggedIn && ! isUserAnonymous {
2020-05-04 19:39:25 +00:00
inactiveLongEnough , err := hasUserBeenInactiveTooLong ( ctx )
2019-04-24 21:52:08 +00:00
if err != nil {
2020-10-26 11:38:08 +00:00
return "" , "" , nil , nil , authentication . NotAuthenticated , fmt . Errorf ( "Unable to check if user has been inactive for a long time: %s" , err )
2019-04-24 21:52:08 +00:00
}
if inactiveLongEnough {
2020-05-02 05:06:39 +00:00
// Destroy the session a new one will be regenerated on next request.
2019-04-24 21:52:08 +00:00
err := ctx . Providers . SessionProvider . DestroySession ( ctx . RequestCtx )
if err != nil {
2020-10-26 11:38:08 +00:00
return "" , "" , nil , nil , authentication . NotAuthenticated , fmt . Errorf ( "Unable to destroy user session after long inactivity: %s" , err )
2019-04-24 21:52:08 +00:00
}
2020-10-26 11:38:08 +00:00
return userSession . Username , userSession . DisplayName , userSession . Groups , userSession . Emails , authentication . NotAuthenticated , fmt . Errorf ( "User %s has been inactive for too long" , userSession . Username )
2019-04-24 21:52:08 +00:00
}
}
2020-05-04 19:39:25 +00:00
err = verifySessionHasUpToDateProfile ( ctx , targetURL , userSession , refreshProfile , refreshProfileInterval )
if err != nil {
if err == authentication . ErrUserNotFound {
err = ctx . Providers . SessionProvider . DestroySession ( ctx . RequestCtx )
if err != nil {
ctx . Logger . Error ( fmt . Errorf ( "Unable to destroy user session after provider refresh didn't find the user: %s" , err ) )
}
2020-05-05 19:35:32 +00:00
2020-10-26 11:38:08 +00:00
return userSession . Username , userSession . DisplayName , userSession . Groups , userSession . Emails , authentication . NotAuthenticated , err
2020-05-04 19:39:25 +00:00
}
2020-05-05 19:35:32 +00:00
2020-05-04 19:39:25 +00:00
ctx . Logger . Warnf ( "Error occurred while attempting to update user details from LDAP: %s" , err )
}
2020-10-26 11:38:08 +00:00
return userSession . Username , userSession . DisplayName , userSession . Groups , userSession . Emails , userSession . AuthenticationLevel , nil
2019-04-24 21:52:08 +00:00
}
2021-03-05 04:18:31 +00:00
func handleUnauthorized ( ctx * middlewares . AutheliaCtx , targetURL fmt . Stringer , isBasicAuth bool , username string , method [ ] byte ) {
2021-02-23 23:35:04 +00:00
if isBasicAuth {
ctx . Logger . Infof ( "Access to %s is not authorized to user %s, sending 401 response with basic auth header" , targetURL . String ( ) , username )
ctx . ReplyUnauthorized ( )
ctx . Response . Header . Add ( "WWW-Authenticate" , "Basic realm=\"Authentication required\"" )
return
}
2020-04-24 23:29:36 +00:00
// Kubernetes ingress controller and Traefik use the rd parameter of the verify
// endpoint to provide the URL of the login portal. The target URL of the user
2020-08-21 01:15:20 +00:00
// is computed from X-Forwarded-* headers or X-Original-URL.
2020-04-24 23:29:36 +00:00
rd := string ( ctx . QueryArgs ( ) . Peek ( "rd" ) )
2021-03-05 04:18:31 +00:00
rm := string ( method )
friendlyMethod := "unknown"
if rm != "" {
friendlyMethod = rm
}
2020-04-24 23:29:36 +00:00
if rd != "" {
2021-03-05 04:18:31 +00:00
redirectionURL := ""
if rm != "" {
redirectionURL = fmt . Sprintf ( "%s?rd=%s&rm=%s" , rd , url . QueryEscape ( targetURL . String ( ) ) , rm )
} else {
redirectionURL = fmt . Sprintf ( "%s?rd=%s" , rd , url . QueryEscape ( targetURL . String ( ) ) )
2020-04-24 23:29:36 +00:00
}
2020-05-05 19:35:32 +00:00
2021-03-05 04:18:31 +00:00
ctx . Logger . Infof ( "Access to %s (method %s) is not authorized to user %s, redirecting to %s" , targetURL . String ( ) , friendlyMethod , username , redirectionURL )
2020-04-24 23:29:36 +00:00
ctx . Redirect ( redirectionURL , 302 )
ctx . SetBodyString ( fmt . Sprintf ( "Found. Redirecting to %s" , redirectionURL ) )
} else {
2021-03-05 04:18:31 +00:00
ctx . Logger . Infof ( "Access to %s (method %s) is not authorized to user %s, sending 401 response" , targetURL . String ( ) , friendlyMethod , username )
2020-04-24 23:29:36 +00:00
ctx . ReplyUnauthorized ( )
}
}
func updateActivityTimestamp ( ctx * middlewares . AutheliaCtx , isBasicAuth bool , username string ) error {
if isBasicAuth || username == "" {
return nil
}
userSession := ctx . GetSession ( )
// We don't need to update the activity timestamp when user checked keep me logged in.
if userSession . KeepMeLoggedIn {
return nil
}
2020-05-02 05:06:39 +00:00
// Mark current activity.
2020-04-24 23:29:36 +00:00
userSession . LastActivity = ctx . Clock . Now ( ) . Unix ( )
2020-05-05 19:35:32 +00:00
2020-04-24 23:29:36 +00:00
return ctx . SaveSession ( userSession )
}
2020-05-04 19:39:25 +00:00
// generateVerifySessionHasUpToDateProfileTraceLogs is used to generate trace logs only when trace logging is enabled.
// The information calculated in this function is completely useless other than trace for now.
func generateVerifySessionHasUpToDateProfileTraceLogs ( ctx * middlewares . AutheliaCtx , userSession * session . UserSession ,
details * authentication . UserDetails ) {
groupsAdded , groupsRemoved := utils . StringSlicesDelta ( userSession . Groups , details . Groups )
emailsAdded , emailsRemoved := utils . StringSlicesDelta ( userSession . Emails , details . Emails )
2020-06-19 10:50:21 +00:00
nameDelta := userSession . DisplayName != details . DisplayName
2020-05-04 19:39:25 +00:00
// Check Groups.
var groupsDelta [ ] string
if len ( groupsAdded ) != 0 {
groupsDelta = append ( groupsDelta , fmt . Sprintf ( "Added: %s." , strings . Join ( groupsAdded , ", " ) ) )
}
2020-05-05 19:35:32 +00:00
2020-05-04 19:39:25 +00:00
if len ( groupsRemoved ) != 0 {
groupsDelta = append ( groupsDelta , fmt . Sprintf ( "Removed: %s." , strings . Join ( groupsRemoved , ", " ) ) )
}
2020-05-05 19:35:32 +00:00
2020-05-04 19:39:25 +00:00
if len ( groupsDelta ) != 0 {
ctx . Logger . Tracef ( "Updated groups detected for %s. %s" , userSession . Username , strings . Join ( groupsDelta , " " ) )
} else {
ctx . Logger . Tracef ( "No updated groups detected for %s" , userSession . Username )
}
2019-04-24 21:52:08 +00:00
2020-05-04 19:39:25 +00:00
// Check Emails.
var emailsDelta [ ] string
if len ( emailsAdded ) != 0 {
emailsDelta = append ( emailsDelta , fmt . Sprintf ( "Added: %s." , strings . Join ( emailsAdded , ", " ) ) )
}
2020-05-05 19:35:32 +00:00
2020-05-04 19:39:25 +00:00
if len ( emailsRemoved ) != 0 {
emailsDelta = append ( emailsDelta , fmt . Sprintf ( "Removed: %s." , strings . Join ( emailsRemoved , ", " ) ) )
2019-04-24 21:52:08 +00:00
}
2020-05-05 19:35:32 +00:00
2020-05-04 19:39:25 +00:00
if len ( emailsDelta ) != 0 {
ctx . Logger . Tracef ( "Updated emails detected for %s. %s" , userSession . Username , strings . Join ( emailsDelta , " " ) )
} else {
ctx . Logger . Tracef ( "No updated emails detected for %s" , userSession . Username )
}
2020-06-19 10:50:21 +00:00
// Check Name.
if nameDelta {
ctx . Logger . Tracef ( "Updated display name detected for %s. Added: %s. Removed: %s." , userSession . Username , details . DisplayName , userSession . DisplayName )
} else {
ctx . Logger . Tracef ( "No updated display name detected for %s" , userSession . Username )
}
2020-05-04 19:39:25 +00:00
}
2019-04-24 21:52:08 +00:00
2020-05-04 19:39:25 +00:00
func verifySessionHasUpToDateProfile ( ctx * middlewares . AutheliaCtx , targetURL * url . URL , userSession * session . UserSession ,
refreshProfile bool , refreshProfileInterval time . Duration ) error {
// TODO: Add a check for LDAP password changes based on a time format attribute.
// See https://docs.authelia.com/security/threat-model.html#potential-future-guarantees
ctx . Logger . Tracef ( "Checking if we need check the authentication backend for an updated profile for %s." , userSession . Username )
2020-05-05 19:35:32 +00:00
2021-02-02 01:01:46 +00:00
if ! refreshProfile || userSession . Username == "" || targetURL == nil {
return nil
}
2020-05-04 19:39:25 +00:00
2021-02-02 01:01:46 +00:00
if refreshProfileInterval != schema . RefreshIntervalAlways && userSession . RefreshTTL . After ( ctx . Clock . Now ( ) ) {
return nil
}
2020-05-04 19:39:25 +00:00
2021-02-02 01:01:46 +00:00
ctx . Logger . Debugf ( "Checking the authentication backend for an updated profile for user %s" , userSession . Username )
details , err := ctx . Providers . UserProvider . GetDetails ( userSession . Username )
// Only update the session if we could get the new details.
if err != nil {
return err
}
emailsDiff := utils . IsStringSlicesDifferent ( userSession . Emails , details . Emails )
groupsDiff := utils . IsStringSlicesDifferent ( userSession . Groups , details . Groups )
nameDiff := userSession . DisplayName != details . DisplayName
if ! groupsDiff && ! emailsDiff && ! nameDiff {
ctx . Logger . Tracef ( "Updated profile not detected for %s." , userSession . Username )
// Only update TTL if the user has a interval set.
// We get to this check when there were no changes.
// Also make sure to update the session even if no difference was found.
// This is so that we don't check every subsequent request after this one.
if refreshProfileInterval != schema . RefreshIntervalAlways {
// Update RefreshTTL and save session if refresh is not set to always.
userSession . RefreshTTL = ctx . Clock . Now ( ) . Add ( refreshProfileInterval )
2020-05-04 19:39:25 +00:00
return ctx . SaveSession ( * userSession )
}
2021-02-02 01:01:46 +00:00
} else {
ctx . Logger . Debugf ( "Updated profile detected for %s." , userSession . Username )
if ctx . Configuration . LogLevel == "trace" {
generateVerifySessionHasUpToDateProfileTraceLogs ( ctx , userSession , details )
}
userSession . Emails = details . Emails
userSession . Groups = details . Groups
userSession . DisplayName = details . DisplayName
// Only update TTL if the user has a interval set.
if refreshProfileInterval != schema . RefreshIntervalAlways {
userSession . RefreshTTL = ctx . Clock . Now ( ) . Add ( refreshProfileInterval )
}
// Return the result of save session if there were changes.
return ctx . SaveSession ( * userSession )
2020-02-18 22:39:07 +00:00
}
2020-05-05 19:35:32 +00:00
2020-05-05 21:27:38 +00:00
// Return nil if disabled or if no changes and refresh interval set to always.
2020-05-04 19:39:25 +00:00
return nil
}
2020-02-18 22:39:07 +00:00
2020-05-04 19:39:25 +00:00
func getProfileRefreshSettings ( cfg schema . AuthenticationBackendConfiguration ) ( refresh bool , refreshInterval time . Duration ) {
if cfg . Ldap != nil {
2021-02-02 01:01:46 +00:00
if cfg . RefreshInterval == schema . ProfileRefreshDisabled {
refresh = false
refreshInterval = 0
} else {
2020-05-04 19:39:25 +00:00
refresh = true
2020-05-05 19:35:32 +00:00
2020-05-04 19:39:25 +00:00
if cfg . RefreshInterval != schema . ProfileRefreshAlways {
// Skip Error Check since validator checks it
refreshInterval , _ = utils . ParseDurationString ( cfg . RefreshInterval )
} else {
refreshInterval = schema . RefreshIntervalAlways
}
}
2020-02-18 22:39:07 +00:00
}
2020-05-05 19:35:32 +00:00
2020-05-04 19:39:25 +00:00
return refresh , refreshInterval
}
2020-02-18 22:39:07 +00:00
2021-02-23 23:35:04 +00:00
func verifyAuth ( ctx * middlewares . AutheliaCtx , targetURL * url . URL , refreshProfile bool , refreshProfileInterval time . Duration ) ( isBasicAuth bool , username , name string , groups , emails [ ] string , authLevel authentication . Level , err error ) {
authHeader := ProxyAuthorizationHeader
if bytes . Equal ( ctx . QueryArgs ( ) . Peek ( "auth" ) , [ ] byte ( "basic" ) ) {
authHeader = AuthorizationHeader
isBasicAuth = true
}
authValue := ctx . Request . Header . Peek ( authHeader )
if authValue != nil {
isBasicAuth = true
} else if isBasicAuth {
err = fmt . Errorf ( "Basic auth requested via query arg, but no value provided via %s header" , authHeader )
return
}
if isBasicAuth {
username , name , groups , emails , authLevel , err = verifyBasicAuth ( authHeader , authValue , * targetURL , ctx )
return
}
userSession := ctx . GetSession ( )
username , name , groups , emails , authLevel , err = verifySessionCookie ( ctx , targetURL , & userSession , refreshProfile , refreshProfileInterval )
sessionUsername := ctx . Request . Header . Peek ( SessionUsernameHeader )
if sessionUsername != nil && ! strings . EqualFold ( string ( sessionUsername ) , username ) {
ctx . Logger . Warnf ( "Possible cookie hijack or attempt to bypass security detected destroying the session and sending 401 response" )
err = ctx . Providers . SessionProvider . DestroySession ( ctx . RequestCtx )
if err != nil {
ctx . Logger . Error (
fmt . Errorf (
"Unable to destroy user session after handler could not match them to their %s header: %s" ,
SessionUsernameHeader , err ) )
}
err = fmt . Errorf ( "Could not match user %s to their %s header with a value of %s when visiting %s" , username , SessionUsernameHeader , sessionUsername , targetURL . String ( ) )
}
return
}
2020-05-04 19:39:25 +00:00
// VerifyGet returns the handler verifying if a request is allowed to go through.
func VerifyGet ( cfg schema . AuthenticationBackendConfiguration ) middlewares . RequestHandler {
refreshProfile , refreshProfileInterval := getProfileRefreshSettings ( cfg )
2019-04-24 21:52:08 +00:00
2020-05-04 19:39:25 +00:00
return func ( ctx * middlewares . AutheliaCtx ) {
ctx . Logger . Tracef ( "Headers=%s" , ctx . Request . Header . String ( ) )
targetURL , err := getOriginalURL ( ctx )
2019-04-24 21:52:08 +00:00
2020-05-04 19:39:25 +00:00
if err != nil {
ctx . Error ( fmt . Errorf ( "Unable to parse target URL: %s" , err ) , operationFailedMessage )
return
}
2019-04-24 21:52:08 +00:00
2020-05-04 19:39:25 +00:00
if ! isSchemeHTTPS ( targetURL ) && ! isSchemeWSS ( targetURL ) {
ctx . Logger . Error ( fmt . Errorf ( "Scheme of target URL %s must be secure since cookies are " +
"only transported over a secure connection for security reasons" , targetURL . String ( ) ) )
ctx . ReplyUnauthorized ( )
2020-05-05 19:35:32 +00:00
2020-04-24 23:29:36 +00:00
return
}
2019-04-24 21:52:08 +00:00
2020-05-04 19:39:25 +00:00
if ! isURLUnderProtectedDomain ( targetURL , ctx . Configuration . Session . Domain ) {
ctx . Logger . Error ( fmt . Errorf ( "The target URL %s is not under the protected domain %s" ,
targetURL . String ( ) , ctx . Configuration . Session . Domain ) )
ctx . ReplyUnauthorized ( )
2020-05-05 19:35:32 +00:00
2020-05-04 19:39:25 +00:00
return
}
2019-04-24 21:52:08 +00:00
2021-02-23 23:35:04 +00:00
isBasicAuth , username , name , groups , emails , authLevel , err := verifyAuth ( ctx , targetURL , refreshProfile , refreshProfileInterval )
2020-05-04 19:39:25 +00:00
2021-03-05 04:18:31 +00:00
method := ctx . XForwardedMethod ( )
2020-05-04 19:39:25 +00:00
if err != nil {
ctx . Logger . Error ( fmt . Sprintf ( "Error caught when verifying user authorization: %s" , err ) )
2020-05-05 19:35:32 +00:00
2020-05-04 19:39:25 +00:00
if err := updateActivityTimestamp ( ctx , isBasicAuth , username ) ; err != nil {
ctx . Error ( fmt . Errorf ( "Unable to update last activity: %s" , err ) , operationFailedMessage )
return
}
2020-05-05 19:35:32 +00:00
2021-03-05 04:18:31 +00:00
handleUnauthorized ( ctx , targetURL , isBasicAuth , username , method )
2020-05-05 19:35:32 +00:00
2020-05-04 19:39:25 +00:00
return
}
2021-03-05 04:18:31 +00:00
authorized := isTargetURLAuthorized ( ctx . Providers . Authorizer , * targetURL , username ,
groups , ctx . RemoteIP ( ) , method , authLevel )
2020-05-04 19:39:25 +00:00
2021-03-05 04:18:31 +00:00
switch authorized {
2020-05-05 21:27:38 +00:00
case Forbidden :
2020-05-04 19:39:25 +00:00
ctx . Logger . Infof ( "Access to %s is forbidden to user %s" , targetURL . String ( ) , username )
ctx . ReplyForbidden ( )
2020-05-05 21:27:38 +00:00
case NotAuthorized :
2021-03-05 04:18:31 +00:00
handleUnauthorized ( ctx , targetURL , isBasicAuth , username , method )
2020-05-05 21:27:38 +00:00
case Authorized :
2020-10-26 11:38:08 +00:00
setForwardedHeaders ( & ctx . Response . Header , username , name , groups , emails )
2020-05-04 19:39:25 +00:00
}
if err := updateActivityTimestamp ( ctx , isBasicAuth , username ) ; err != nil {
ctx . Error ( fmt . Errorf ( "Unable to update last activity: %s" , err ) , operationFailedMessage )
}
2019-04-24 21:52:08 +00:00
}
}