grafana/pkg/services/authn/authn.go
Karl Persson 9fbb29c588
AuthN: Add client to perform basic authentication (#60877)
* AuthN: Add basic auth client boilerplate

* AuthN: Implement test function for basic auth client

* AuthN: Implement the authentication method for basic auth

* AuthN: Add tests for basic auth authentication

* ContextHandler: perform basic auth authentication through authn service
if feature toggle is enabled

* AuthN: Add providers for sync services and pass required dependencies
2023-01-03 10:23:38 +01:00

167 lines
4.0 KiB
Go

package authn
import (
"context"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/user"
"golang.org/x/oauth2"
)
const (
ClientAPIKey = "auth.client.api-key" // #nosec G101
ClientAnonymous = "auth.client.anonymous"
ClientBasic = "auth.client.basic"
)
type ClientParams struct {
SyncUser bool
AllowSignUp bool
EnableDisabledUsers bool
}
type PostAuthHookFn func(ctx context.Context, clientParams *ClientParams, identity *Identity) error
type Service interface {
// RegisterPostAuthHook registers a hook that is called after a successful authentication.
RegisterPostAuthHook(hook PostAuthHookFn)
// Authenticate authenticates a request using the specified client.
Authenticate(ctx context.Context, client string, r *Request) (*Identity, bool, error)
}
type Client interface {
// Authenticate performs the authentication for the request
Authenticate(ctx context.Context, r *Request) (*Identity, error)
ClientParams() *ClientParams
// Test should return true if client can be used to authenticate request
Test(ctx context.Context, r *Request) bool
}
type Request struct {
// OrgID will be populated by authn.Service
OrgID int64
HTTPRequest *http.Request
}
const (
NamespaceUser = "user"
NamespaceAPIKey = "api-key"
NamespaceServiceAccount = "service-account"
)
type Identity struct {
OrgID int64
OrgCount int
OrgName string
OrgRoles map[int64]org.RoleType
ID string
Login string
Name string
Email string
IsGrafanaAdmin *bool
AuthModule string // AuthModule is the name of the external system
AuthID string // AuthId is the unique identifier for the user in the external system
OAuthToken *oauth2.Token
LookUpParams models.UserLookupParams
IsDisabled bool
HelpFlags1 user.HelpFlags1
LastSeenAt time.Time
Teams []int64
}
func (i *Identity) Role() org.RoleType {
return i.OrgRoles[i.OrgID]
}
// IsAnonymous will return true if no ID is set on the identity
func (i *Identity) IsAnonymous() bool {
return i.ID == ""
}
// TODO: improve error handling
func (i *Identity) NamespacedID() (string, int64) {
var (
id int64
namespace string
)
split := strings.Split(i.ID, ":")
if len(split) != 2 {
return "", -1
}
id, errI := strconv.ParseInt(split[1], 10, 64)
if errI != nil {
return "", -1
}
namespace = split[0]
return namespace, id
}
func (i *Identity) SignedInUser() *user.SignedInUser {
var isGrafanaAdmin bool
if i.IsGrafanaAdmin != nil {
isGrafanaAdmin = *i.IsGrafanaAdmin
}
u := &user.SignedInUser{
UserID: 0,
OrgID: i.OrgID,
OrgName: i.OrgName,
OrgRole: i.Role(),
ExternalAuthModule: i.AuthModule,
ExternalAuthID: i.AuthID,
Login: i.Login,
Name: i.Name,
Email: i.Email,
OrgCount: i.OrgCount,
IsGrafanaAdmin: isGrafanaAdmin,
IsAnonymous: i.IsAnonymous(),
IsDisabled: i.IsDisabled,
HelpFlags1: i.HelpFlags1,
LastSeenAt: i.LastSeenAt,
Teams: i.Teams,
}
namespace, id := i.NamespacedID()
if namespace == NamespaceAPIKey {
u.ApiKeyID = id
} else {
u.UserID = id
u.IsServiceAccount = namespace == NamespaceServiceAccount
}
return u
}
func NamespacedID(namespace string, id int64) string {
return fmt.Sprintf("%s:%d", namespace, id)
}
func IdentityFromSignedInUser(id string, usr *user.SignedInUser) *Identity {
return &Identity{
ID: id,
OrgID: usr.OrgID,
OrgName: usr.OrgName,
OrgRoles: map[int64]org.RoleType{usr.OrgID: usr.OrgRole},
Login: usr.Login,
Name: usr.Name,
Email: usr.Email,
OrgCount: usr.OrgCount,
IsGrafanaAdmin: &usr.IsGrafanaAdmin,
IsDisabled: usr.IsDisabled,
HelpFlags1: usr.HelpFlags1,
LastSeenAt: usr.LastSeenAt,
Teams: usr.Teams,
}
}