2023-01-03 03:23:38 -06:00
package clients
import (
"context"
2023-01-09 09:40:29 -06:00
"errors"
2023-01-03 03:23:38 -06:00
"strings"
"github.com/grafana/grafana/pkg/services/authn"
"github.com/grafana/grafana/pkg/services/loginattempt"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/util/errutil"
2023-01-10 07:55:27 -06:00
"github.com/grafana/grafana/pkg/web"
2023-01-03 03:23:38 -06:00
)
var (
2023-01-09 09:40:29 -06:00
errDecodingBasicAuthHeader = errutil . NewBase ( errutil . StatusBadRequest , "basic-auth.invalid-header" , errutil . WithPublicMessage ( "Invalid Basic Auth Header" ) )
errBasicAuthCredentials = errutil . NewBase ( errutil . StatusUnauthorized , "basic-auth.invalid-credentials" , errutil . WithPublicMessage ( "Invalid username or password" ) )
2023-01-03 03:23:38 -06:00
)
var _ authn . Client = new ( Basic )
2023-01-09 09:40:29 -06:00
func ProvideBasic ( loginAttempts loginattempt . Service , clients ... authn . PasswordClient ) * Basic {
return & Basic { clients , loginAttempts }
2023-01-03 03:23:38 -06:00
}
type Basic struct {
2023-01-09 09:40:29 -06:00
clients [ ] authn . PasswordClient
2023-01-03 03:23:38 -06:00
loginAttempts loginattempt . Service
}
func ( c * Basic ) Authenticate ( ctx context . Context , r * authn . Request ) ( * authn . Identity , error ) {
username , password , err := util . DecodeBasicAuthHeader ( getBasicAuthHeaderFromRequest ( r ) )
if err != nil {
2023-01-09 09:40:29 -06:00
return nil , errDecodingBasicAuthHeader . Errorf ( "failed to decode basic auth header: %w" , err )
2023-01-03 03:23:38 -06:00
}
2023-01-12 08:02:04 -06:00
r . SetMeta ( authn . MetaKeyUsername , username )
2023-01-03 03:23:38 -06:00
ok , err := c . loginAttempts . Validate ( ctx , username )
if err != nil {
return nil , err
}
if ! ok {
2023-01-09 09:40:29 -06:00
return nil , errBasicAuthCredentials . Errorf ( "too many consecutive incorrect login attempts for user - login for user temporarily blocked" )
2023-01-03 03:23:38 -06:00
}
if len ( password ) == 0 {
2023-01-09 09:40:29 -06:00
return nil , errBasicAuthCredentials . Errorf ( "no password provided" )
2023-01-03 03:23:38 -06:00
}
2023-01-09 09:40:29 -06:00
for _ , pwClient := range c . clients {
2023-01-12 08:02:04 -06:00
identity , err := pwClient . AuthenticatePassword ( ctx , r , username , password )
2023-01-09 09:40:29 -06:00
if err != nil {
if errors . Is ( err , errIdentityNotFound ) {
// continue to next password client if identity could not be found
continue
}
if errors . Is ( err , errInvalidPassword ) {
// only add login attempt if identity was found but the provided password was invalid
2023-01-10 07:55:27 -06:00
_ = c . loginAttempts . Add ( ctx , username , web . RemoteAddr ( r . HTTPRequest ) )
2023-01-09 09:40:29 -06:00
}
return nil , errBasicAuthCredentials . Errorf ( "failed to authenticate identity: %w" , err )
}
return identity , nil
2023-01-03 03:23:38 -06:00
}
2023-01-09 09:40:29 -06:00
return nil , errBasicAuthCredentials . Errorf ( "failed to authenticate identity using basic auth" )
2023-01-03 03:23:38 -06:00
}
func ( c * Basic ) Test ( ctx context . Context , r * authn . Request ) bool {
2023-01-09 09:40:29 -06:00
if len ( c . clients ) == 0 {
return false
}
2023-01-03 03:23:38 -06:00
return looksLikeBasicAuthRequest ( r )
}
func looksLikeBasicAuthRequest ( r * authn . Request ) bool {
return getBasicAuthHeaderFromRequest ( r ) != ""
}
func getBasicAuthHeaderFromRequest ( r * authn . Request ) string {
if r . HTTPRequest == nil {
return ""
}
header := r . HTTPRequest . Header . Get ( authorizationHeaderName )
if header == "" {
return ""
}
if ! strings . HasPrefix ( header , basicPrefix ) {
return ""
}
return header
}