@ -2,9 +2,12 @@ package server
import (
"context"
"errors"
"fmt"
authzv1 "github.com/grafana/authlib/authz/proto/v1"
openfgav1 "github.com/openfga/api/proto/openfga/v1"
"google.golang.org/protobuf/types/known/structpb"
"github.com/grafana/grafana/pkg/services/authz/zanzana/common"
)
@ -13,26 +16,36 @@ func (s *Server) Check(ctx context.Context, r *authzv1.CheckRequest) (*authzv1.C
ctx , span := s . tracer . Start ( ctx , "server.Check" )
defer span . End ( )
res , err := s . check ( ctx , r )
if err != nil {
s . logger . Error ( "failed to perform check request" , "error" , err , "namespace" , r . GetNamespace ( ) )
return nil , errors . New ( "failed to perform check request" )
}
return res , nil
}
func ( s * Server ) check ( ctx context . Context , r * authzv1 . CheckRequest ) ( * authzv1 . CheckResponse , error ) {
if err := authorize ( ctx , r . GetNamespace ( ) ) ; err != nil {
return nil , err
}
store , err := s . getStoreInfo ( ctx , r . GetNamespace ( ) )
if err != nil {
return nil , err
return nil , fmt . Errorf ( "failed to get openfga store: %w" , err )
}
relation := common . VerbMapping [ r . GetVerb ( ) ]
contextuals , err := s . getContextuals ( r . GetSubject ( ) )
if err != nil {
return nil , err
return nil , fmt . Errorf ( "failed to get contextual tuples: %w" , err )
}
resource := common . NewResourceInfoFromCheck ( r )
res , err := s . checkGroupResource ( ctx , r . GetSubject ( ) , relation , resource , contextuals , store )
if err != nil {
return nil , err
return nil , fmt . Errorf ( "failed to check group resource: %w" , err )
}
if res . GetAllowed ( ) {
@ -40,10 +53,18 @@ func (s *Server) Check(ctx context.Context, r *authzv1.CheckRequest) (*authzv1.C
}
if resource . IsGeneric ( ) {
return s . checkGeneric ( ctx , r . GetSubject ( ) , relation , resource , contextuals , store )
res , err = s . checkGeneric ( ctx , r . GetSubject ( ) , relation , resource , contextuals , store )
if err != nil {
return nil , fmt . Errorf ( "failed to check generic resource: %w" , err )
}
return res , nil
}
return s . checkTyped ( ctx , r . GetSubject ( ) , relation , resource , contextuals , store )
res , err = s . checkTyped ( ctx , r . GetSubject ( ) , relation , resource , contextuals , store )
if err != nil {
return nil , fmt . Errorf ( "failed to check typed resource: %w" , err )
}
return res , nil
}
// checkGroupResource check if subject has access to the full "GroupResource", if they do they can access every object
@ -53,16 +74,7 @@ func (s *Server) checkGroupResource(ctx context.Context, subject, relation strin
return & authzv1 . CheckResponse { Allowed : false } , nil
}
res , err := s . openfga . Check ( ctx , & openfgav1 . CheckRequest {
StoreId : store . ID ,
AuthorizationModelId : store . ModelID ,
TupleKey : & openfgav1 . CheckRequestTupleKey {
User : subject ,
Relation : relation ,
Object : resource . GroupResourceIdent ( ) ,
} ,
ContextualTuples : contextuals ,
} )
res , err := s . openfgaCheck ( ctx , store , subject , relation , resource . GroupResourceIdent ( ) , contextuals , nil )
if err != nil {
return nil , err
}
@ -84,18 +96,7 @@ func (s *Server) checkTyped(ctx context.Context, subject, relation string, resou
if resource . HasSubresource ( ) {
// Check if subject has access as a subresource
res , err := s . openfga . Check ( ctx , & openfgav1 . CheckRequest {
StoreId : store . ID ,
AuthorizationModelId : store . ModelID ,
TupleKey : & openfgav1 . CheckRequestTupleKey {
User : subject ,
Relation : subresourceRelation ,
Object : resourceIdent ,
} ,
Context : resourceCtx ,
ContextualTuples : contextuals ,
} )
res , err := s . openfgaCheck ( ctx , store , subject , subresourceRelation , resourceIdent , contextuals , resourceCtx )
if err != nil {
return nil , err
}
@ -110,25 +111,12 @@ func (s *Server) checkTyped(ctx context.Context, subject, relation string, resou
}
// Check if subject has direct access to resource
res , err := s . openfga . Check ( ctx , & openfgav1 . CheckRequest {
StoreId : store . ID ,
AuthorizationModelId : store . ModelID ,
TupleKey : & openfgav1 . CheckRequestTupleKey {
User : subject ,
Relation : relation ,
Object : resourceIdent ,
} ,
ContextualTuples : contextuals ,
} )
res , err := s . openfgaCheck ( ctx , store , subject , relation , resourceIdent , contextuals , nil )
if err != nil {
return nil , err
}
if res . GetAllowed ( ) {
return & authzv1 . CheckResponse { Allowed : true } , nil
}
return & authzv1 . CheckResponse { Allowed : false } , nil
return & authzv1 . CheckResponse { Allowed : res . GetAllowed ( ) } , nil
}
// checkGeneric check our generic "resource" type. It checks:
@ -143,18 +131,7 @@ func (s *Server) checkGeneric(ctx context.Context, subject, relation string, res
if folderIdent != "" && common . IsSubresourceRelation ( folderRelation ) {
// Check if subject has access as a sub resource for the folder
res , err := s . openfga . Check ( ctx , & openfgav1 . CheckRequest {
StoreId : store . ID ,
AuthorizationModelId : store . ModelID ,
TupleKey : & openfgav1 . CheckRequestTupleKey {
User : subject ,
Relation : folderRelation ,
Object : folderIdent ,
} ,
Context : resourceCtx ,
ContextualTuples : contextuals ,
} )
res , err := s . openfgaCheck ( ctx , store , subject , folderRelation , folderIdent , contextuals , resourceCtx )
if err != nil {
return nil , err
}
@ -170,21 +147,33 @@ func (s *Server) checkGeneric(ctx context.Context, subject, relation string, res
}
// Check if subject has direct access to resource
res , err := s . openfgaCheck ( ctx , store , subject , relation , resourceIdent , contextuals , resourceCtx )
if err != nil {
return nil , err
}
return & authzv1 . CheckResponse { Allowed : res . GetAllowed ( ) } , nil
}
func ( s * Server ) openfgaCheck ( ctx context . Context , store * storeInfo , subject , relation , object string , contextuals * openfgav1 . ContextualTupleKeys , resourceCtx * structpb . Struct ) ( * openfgav1 . CheckResponse , error ) {
res , err := s . openfga . Check ( ctx , & openfgav1 . CheckRequest {
StoreId : store . ID ,
AuthorizationModelId : store . ModelID ,
TupleKey : & openfgav1 . CheckRequestTupleKey {
User : subject ,
Relation : relation ,
Object : resourceIdent ,
Object : objec t,
} ,
Context : resourceCtx ,
ContextualTuples : contextuals ,
} )
if err != nil {
return nil , err
// error is decorated by openfga with a public-facing error message, so we need to unwrap it to get the actual error and log it server-side,
// but we want to return wrapped error to the client to prevent leaking internal error details
s . logger . Error ( "failed to perform check" , "error" , errors . Unwrap ( err ) , "subject" , subject , "relation" , relation , "object" , object )
return nil , fmt . Errorf ( "failed to perform openfga Check request: %w" , err )
}
return & authzv1 . CheckResponse { Allowed : res . GetAllowed ( ) } , nil
return res , nil
}