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  }