github.com/openfga/openfga@v1.5.4-rc1/pkg/server/list_users.go (about) 1 package server 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "strings" 8 9 grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags" 10 openfgav1 "github.com/openfga/api/proto/openfga/v1" 11 "go.opentelemetry.io/otel/attribute" 12 "go.opentelemetry.io/otel/trace" 13 "google.golang.org/grpc/codes" 14 "google.golang.org/grpc/status" 15 16 "github.com/openfga/openfga/internal/condition" 17 "github.com/openfga/openfga/internal/graph" 18 "github.com/openfga/openfga/pkg/middleware/validator" 19 "github.com/openfga/openfga/pkg/telemetry" 20 21 "github.com/openfga/openfga/pkg/server/commands/listusers" 22 serverErrors "github.com/openfga/openfga/pkg/server/errors" 23 "github.com/openfga/openfga/pkg/typesystem" 24 ) 25 26 // ListUsers returns all subjects (users) of a specified terminal type 27 // that are relate via specific relation to a specific object. 28 func (s *Server) ListUsers( 29 ctx context.Context, 30 req *openfgav1.ListUsersRequest, 31 ) (*openfgav1.ListUsersResponse, error) { 32 ctx, span := tracer.Start(ctx, "ListUsers", trace.WithAttributes( 33 attribute.String("object", fmt.Sprintf("%s:%s", req.GetObject().GetType(), req.GetObject().GetId())), 34 attribute.String("relation", req.GetRelation()), 35 attribute.String("user_filters", userFiltersToString(req.GetUserFilters())), 36 )) 37 defer span.End() 38 if !s.IsExperimentallyEnabled(ExperimentalEnableListUsers) { 39 return nil, status.Error(codes.Unimplemented, "ListUsers is not enabled. It can be enabled for experimental use by passing the `--experimentals enable-list-users` configuration option when running OpenFGA server") 40 } 41 42 if !validator.RequestIsValidatedFromContext(ctx) { 43 if err := req.Validate(); err != nil { 44 return nil, status.Error(codes.InvalidArgument, err.Error()) 45 } 46 } 47 48 typesys, err := s.resolveTypesystem(ctx, req.GetStoreId(), req.GetAuthorizationModelId()) 49 if err != nil { 50 telemetry.TraceError(span, err) 51 return nil, err 52 } 53 54 err = listusers.ValidateListUsersRequest(ctx, req, typesys) 55 if err != nil { 56 telemetry.TraceError(span, err) 57 return nil, err 58 } 59 60 ctx = typesystem.ContextWithTypesystem(ctx, typesys) 61 62 listUsersQuery := listusers.NewListUsersQuery(s.datastore, 63 listusers.WithResolveNodeLimit(s.resolveNodeLimit), 64 listusers.WithResolveNodeBreadthLimit(s.resolveNodeBreadthLimit), 65 listusers.WithListUsersQueryLogger(s.logger), 66 listusers.WithListUsersMaxResults(s.listUsersMaxResults), 67 listusers.WithListUsersDeadline(s.listUsersDeadline), 68 listusers.WithListUsersMaxConcurrentReads(s.maxConcurrentReadsForListUsers), 69 ) 70 71 resp, err := listUsersQuery.ListUsers(ctx, req) 72 if err != nil { 73 telemetry.TraceError(span, err) 74 75 switch { 76 case errors.Is(err, graph.ErrResolutionDepthExceeded): 77 return nil, serverErrors.AuthorizationModelResolutionTooComplex 78 case errors.Is(err, condition.ErrEvaluationFailed): 79 return nil, serverErrors.ValidationError(err) 80 default: 81 return nil, serverErrors.HandleError("", err) 82 } 83 } 84 85 datastoreQueryCount := float64(resp.Metadata.DatastoreQueryCount) 86 87 grpc_ctxtags.Extract(ctx).Set(datastoreQueryCountHistogramName, datastoreQueryCount) 88 span.SetAttributes(attribute.Float64(datastoreQueryCountHistogramName, datastoreQueryCount)) 89 datastoreQueryCountHistogram.WithLabelValues( 90 s.serviceName, 91 "list-users", 92 ).Observe(datastoreQueryCount) 93 94 return &openfgav1.ListUsersResponse{ 95 Users: resp.GetUsers(), 96 }, nil 97 } 98 99 func userFiltersToString(filter []*openfgav1.UserTypeFilter) string { 100 var s strings.Builder 101 for _, f := range filter { 102 s.WriteString(f.GetType()) 103 if f.GetRelation() != "" { 104 s.WriteString("#" + f.GetRelation()) 105 } 106 } 107 return s.String() 108 }