package anonimpl import ( "context" "errors" "net/http" "strings" claims "github.com/grafana/authlib/types" "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/anonymous" "github.com/grafana/grafana/pkg/services/anonymous/anonimpl/anonstore" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/setting" ) var ( errInvalidOrg = errutil.Unauthorized("anonymous.invalid-org") errInvalidID = errutil.Unauthorized("anonymous.invalid-id") errDeviceLimit = errutil.Unauthorized("anonymous.device-limit-reached", errutil.WithPublicMessage("Anonymous device limit reached. Contact Administrator")) ) var ( _ authn.ContextAwareClient = new(Anonymous) _ authn.IdentityResolverClient = new(Anonymous) ) type Anonymous struct { cfg *setting.Cfg log log.Logger orgService org.Service anonDeviceService anonymous.Service } func (a *Anonymous) Name() string { return authn.ClientAnonymous } func (a *Anonymous) Authenticate(ctx context.Context, r *authn.Request) (*authn.Identity, error) { o, err := a.orgService.GetByName(ctx, &org.GetOrgByNameQuery{Name: a.cfg.Anonymous.OrgName}) if err != nil { a.log.FromContext(ctx).Error("Failed to find organization", "name", a.cfg.Anonymous.OrgName, "error", err) return nil, err } httpReqCopy := &http.Request{} if r.HTTPRequest != nil && r.HTTPRequest.Header != nil { // avoid r.HTTPRequest.Clone(context.Background()) as we do not require a full clone httpReqCopy.Header = r.HTTPRequest.Header.Clone() httpReqCopy.RemoteAddr = r.HTTPRequest.RemoteAddr } if err := a.anonDeviceService.TagDevice(ctx, httpReqCopy, anonymous.AnonDeviceUI); err != nil { if errors.Is(err, anonstore.ErrDeviceLimitReached) { return nil, errDeviceLimit.Errorf("limit reached for anonymous devices: %w", err) } a.log.Warn("Failed to tag anonymous session", "error", err) } return a.newAnonymousIdentity(o), nil } func (a *Anonymous) IsEnabled() bool { return a.cfg.Anonymous.Enabled } func (a *Anonymous) Test(ctx context.Context, r *authn.Request) bool { // If anonymous client is register it can always be used for authentication return true } func (a *Anonymous) IdentityType() claims.IdentityType { return claims.TypeAnonymous } func (a *Anonymous) ResolveIdentity(ctx context.Context, orgID int64, typ claims.IdentityType, id string) (*authn.Identity, error) { o, err := a.orgService.GetByName(ctx, &org.GetOrgByNameQuery{Name: a.cfg.Anonymous.OrgName}) if err != nil { return nil, err } if o.ID != orgID { return nil, errInvalidOrg.Errorf("anonymous user cannot authenticate in org %d", o.ID) } // Anonymous identities should always have the same namespace id. if !claims.IsIdentityType(typ, claims.TypeAnonymous) || id != "0" { return nil, errInvalidID } return a.newAnonymousIdentity(o), nil } func (a *Anonymous) UsageStatFn(ctx context.Context) (map[string]any, error) { m := map[string]any{} // Add stats about anonymous auth m["stats.anonymous.customized_role.count"] = 0 if !strings.EqualFold(a.cfg.Anonymous.OrgRole, "Viewer") { m["stats.anonymous.customized_role.count"] = 1 } return m, nil } func (a *Anonymous) Priority() uint { return 100 } func (a *Anonymous) newAnonymousIdentity(o *org.Org) *authn.Identity { return &authn.Identity{ ID: "0", Type: claims.TypeAnonymous, OrgID: o.ID, OrgName: o.Name, OrgRoles: map[int64]org.RoleType{o.ID: org.RoleType(a.cfg.Anonymous.OrgRole)}, ClientParams: authn.ClientParams{SyncPermissions: true}, } }