github.com/openfga/openfga@v1.5.4-rc1/pkg/tuple/tuple.go (about)

     1  // Package tuple contains code to manipulate tuples and errors related to tuples.
     2  package tuple
     3  
     4  import (
     5  	"fmt"
     6  	"regexp"
     7  	"strings"
     8  
     9  	openfgav1 "github.com/openfga/api/proto/openfga/v1"
    10  	"google.golang.org/protobuf/types/known/structpb"
    11  )
    12  
    13  type TupleWithCondition interface {
    14  	TupleWithoutCondition
    15  	GetCondition() *openfgav1.RelationshipCondition
    16  }
    17  
    18  type TupleWithoutCondition interface {
    19  	GetUser() string
    20  	GetObject() string
    21  	GetRelation() string
    22  	String() string
    23  }
    24  
    25  type UserType string
    26  
    27  const (
    28  	User    UserType = "user"
    29  	UserSet UserType = "userset"
    30  )
    31  
    32  const Wildcard = "*"
    33  
    34  var (
    35  	userIDRegex   = regexp.MustCompile(`^[^:#\s]+$`)
    36  	objectRegex   = regexp.MustCompile(`^[^:#\s]+:[^#:\s]+$`)
    37  	userSetRegex  = regexp.MustCompile(`^[^:#\s]+:[^#\s]+#[^:#\s]+$`)
    38  	relationRegex = regexp.MustCompile(`^[^:#@\s]+$`)
    39  )
    40  
    41  func ConvertCheckRequestTupleKeyToTupleKey(tk *openfgav1.CheckRequestTupleKey) *openfgav1.TupleKey {
    42  	return &openfgav1.TupleKey{
    43  		Object:   tk.GetObject(),
    44  		Relation: tk.GetRelation(),
    45  		User:     tk.GetUser(),
    46  	}
    47  }
    48  
    49  func ConvertAssertionTupleKeyToTupleKey(tk *openfgav1.AssertionTupleKey) *openfgav1.TupleKey {
    50  	return &openfgav1.TupleKey{
    51  		Object:   tk.GetObject(),
    52  		Relation: tk.GetRelation(),
    53  		User:     tk.GetUser(),
    54  	}
    55  }
    56  
    57  func ConvertReadRequestTupleKeyToTupleKey(tk *openfgav1.ReadRequestTupleKey) *openfgav1.TupleKey {
    58  	return &openfgav1.TupleKey{
    59  		Object:   tk.GetObject(),
    60  		Relation: tk.GetRelation(),
    61  		User:     tk.GetUser(),
    62  	}
    63  }
    64  
    65  func TupleKeyToTupleKeyWithoutCondition(tk *openfgav1.TupleKey) *openfgav1.TupleKeyWithoutCondition {
    66  	return &openfgav1.TupleKeyWithoutCondition{
    67  		Object:   tk.GetObject(),
    68  		Relation: tk.GetRelation(),
    69  		User:     tk.GetUser(),
    70  	}
    71  }
    72  
    73  func TupleKeyWithoutConditionToTupleKey(tk *openfgav1.TupleKeyWithoutCondition) *openfgav1.TupleKey {
    74  	return &openfgav1.TupleKey{
    75  		Object:   tk.GetObject(),
    76  		Relation: tk.GetRelation(),
    77  		User:     tk.GetUser(),
    78  	}
    79  }
    80  
    81  func TupleKeysWithoutConditionToTupleKeys(tks ...*openfgav1.TupleKeyWithoutCondition) []*openfgav1.TupleKey {
    82  	converted := make([]*openfgav1.TupleKey, 0, len(tks))
    83  	for _, tk := range tks {
    84  		converted = append(converted, TupleKeyWithoutConditionToTupleKey(tk))
    85  	}
    86  
    87  	return converted
    88  }
    89  
    90  func NewTupleKey(object, relation, user string) *openfgav1.TupleKey {
    91  	return &openfgav1.TupleKey{
    92  		Object:   object,
    93  		Relation: relation,
    94  		User:     user,
    95  	}
    96  }
    97  
    98  func NewTupleKeyWithCondition(
    99  	object, relation, user, conditionName string,
   100  	context *structpb.Struct,
   101  ) *openfgav1.TupleKey {
   102  	return &openfgav1.TupleKey{
   103  		Object:    object,
   104  		Relation:  relation,
   105  		User:      user,
   106  		Condition: NewRelationshipCondition(conditionName, context),
   107  	}
   108  }
   109  
   110  func NewRelationshipCondition(name string, context *structpb.Struct) *openfgav1.RelationshipCondition {
   111  	if name == "" {
   112  		return nil
   113  	}
   114  
   115  	if context == nil {
   116  		return &openfgav1.RelationshipCondition{
   117  			Name:    name,
   118  			Context: &structpb.Struct{},
   119  		}
   120  	}
   121  
   122  	return &openfgav1.RelationshipCondition{
   123  		Name:    name,
   124  		Context: context,
   125  	}
   126  }
   127  
   128  func NewAssertionTupleKey(object, relation, user string) *openfgav1.AssertionTupleKey {
   129  	return &openfgav1.AssertionTupleKey{
   130  		Object:   object,
   131  		Relation: relation,
   132  		User:     user,
   133  	}
   134  }
   135  
   136  func NewCheckRequestTupleKey(object, relation, user string) *openfgav1.CheckRequestTupleKey {
   137  	return &openfgav1.CheckRequestTupleKey{
   138  		Object:   object,
   139  		Relation: relation,
   140  		User:     user,
   141  	}
   142  }
   143  
   144  func NewExpandRequestTupleKey(object, relation string) *openfgav1.ExpandRequestTupleKey {
   145  	return &openfgav1.ExpandRequestTupleKey{
   146  		Object:   object,
   147  		Relation: relation,
   148  	}
   149  }
   150  
   151  // ObjectKey returns the canonical key for the provided Object. The ObjectKey of an object
   152  // is the string 'objectType:objectId'.
   153  func ObjectKey(obj *openfgav1.Object) string {
   154  	return BuildObject(obj.GetType(), obj.GetId())
   155  }
   156  
   157  type UserString = string
   158  
   159  // UserProtoToString returns a string from a User proto. Ex: 'user:maria' or 'group:fga#member'. It is
   160  // the opposite from StringToUserProto function.
   161  func UserProtoToString(obj *openfgav1.User) UserString {
   162  	switch obj.GetUser().(type) {
   163  	case *openfgav1.User_Wildcard:
   164  		return fmt.Sprintf("%s:*", obj.GetWildcard().GetType())
   165  	case *openfgav1.User_Userset:
   166  		us := obj.GetUser().(*openfgav1.User_Userset)
   167  		return fmt.Sprintf("%s:%s#%s", us.Userset.GetType(), us.Userset.GetId(), us.Userset.GetRelation())
   168  	case *openfgav1.User_Object:
   169  		us := obj.GetUser().(*openfgav1.User_Object)
   170  		return fmt.Sprintf("%s:%s", us.Object.GetType(), us.Object.GetId())
   171  	default:
   172  		panic("unsupported type")
   173  	}
   174  }
   175  
   176  // StringToUserProto returns a User proto from a string. Ex: 'user:maria#member'.
   177  // It is the opposite from FromUserProto function.
   178  func StringToUserProto(userKey UserString) *openfgav1.User {
   179  	userObj, userRel := SplitObjectRelation(userKey)
   180  	userObjType, userObjID := SplitObject(userObj)
   181  	if userRel == "" && userObjID == "*" {
   182  		return &openfgav1.User{User: &openfgav1.User_Wildcard{
   183  			Wildcard: &openfgav1.TypedWildcard{
   184  				Type: userObjType,
   185  			},
   186  		}}
   187  	}
   188  	if userRel == "" {
   189  		return &openfgav1.User{User: &openfgav1.User_Object{Object: &openfgav1.Object{
   190  			Type: userObjType,
   191  			Id:   userObjID,
   192  		}}}
   193  	}
   194  	return &openfgav1.User{User: &openfgav1.User_Userset{Userset: &openfgav1.UsersetUser{
   195  		Type:     userObjType,
   196  		Id:       userObjID,
   197  		Relation: userRel,
   198  	}}}
   199  }
   200  
   201  // SplitObject splits an object into an objectType and an objectID. If no type is present, it returns the empty string
   202  // and the original object.
   203  func SplitObject(object string) (string, string) {
   204  	switch i := strings.IndexByte(object, ':'); i {
   205  	case -1:
   206  		return "", object
   207  	case len(object) - 1:
   208  		return object[0:i], ""
   209  	default:
   210  		return object[0:i], object[i+1:]
   211  	}
   212  }
   213  
   214  func BuildObject(objectType, objectID string) string {
   215  	return fmt.Sprintf("%s:%s", objectType, objectID)
   216  }
   217  
   218  // GetObjectRelationAsString returns a string like "object#relation". If there is no relation it returns "object".
   219  func GetObjectRelationAsString(objectRelation *openfgav1.ObjectRelation) string {
   220  	if objectRelation.GetRelation() != "" {
   221  		return fmt.Sprintf("%s#%s", objectRelation.GetObject(), objectRelation.GetRelation())
   222  	}
   223  	return objectRelation.GetObject()
   224  }
   225  
   226  // SplitObjectRelation splits an object relation string into an object ID and relation name. If no relation is present,
   227  // it returns the original string and an empty relation.
   228  func SplitObjectRelation(objectRelation string) (string, string) {
   229  	switch i := strings.LastIndexByte(objectRelation, '#'); i {
   230  	case -1:
   231  		return objectRelation, ""
   232  	case len(objectRelation) - 1:
   233  		return objectRelation[0:i], ""
   234  	default:
   235  		return objectRelation[0:i], objectRelation[i+1:]
   236  	}
   237  }
   238  
   239  // GetType returns the type from a supplied Object identifier or an empty string if the object id does not contain a
   240  // type.
   241  func GetType(objectID string) string {
   242  	t, _ := SplitObject(objectID)
   243  	return t
   244  }
   245  
   246  // GetRelation returns the 'relation' portion of an object relation string (e.g. `object#relation`), which may be empty if the input is malformed
   247  // (or does not contain a relation).
   248  func GetRelation(objectRelation string) string {
   249  	_, relation := SplitObjectRelation(objectRelation)
   250  	return relation
   251  }
   252  
   253  // IsObjectRelation returns true if the given string specifies a valid object and relation.
   254  func IsObjectRelation(userset string) bool {
   255  	return GetType(userset) != "" && GetRelation(userset) != ""
   256  }
   257  
   258  // ToObjectRelationString formats an object/relation pair as an object#relation string. This is the inverse of
   259  // SplitObjectRelation.
   260  func ToObjectRelationString(object, relation string) string {
   261  	return fmt.Sprintf("%s#%s", object, relation)
   262  }
   263  
   264  // GetUserTypeFromUser returns the type of user (userset or user).
   265  func GetUserTypeFromUser(user string) UserType {
   266  	if IsObjectRelation(user) || IsWildcard(user) {
   267  		return UserSet
   268  	}
   269  	return User
   270  }
   271  
   272  // TupleKeyToString converts a tuple key into its string representation. It assumes the tupleKey is valid
   273  // (i.e. no forbidden characters).
   274  func TupleKeyToString(tk TupleWithoutCondition) string {
   275  	return fmt.Sprintf("%s#%s@%s", tk.GetObject(), tk.GetRelation(), tk.GetUser())
   276  }
   277  
   278  // TupleKeyWithConditionToString converts a tuple key with condition into its string representation. It assumes the tupleKey is valid
   279  // (i.e. no forbidden characters).
   280  func TupleKeyWithConditionToString(tk TupleWithCondition) string {
   281  	return fmt.Sprintf("%s#%s@%s (condition %s)", tk.GetObject(), tk.GetRelation(), tk.GetUser(), tk.GetCondition())
   282  }
   283  
   284  // IsValidObject determines if a string s is a valid object. A valid object contains exactly one `:` and no `#` or spaces.
   285  func IsValidObject(s string) bool {
   286  	return objectRegex.MatchString(s)
   287  }
   288  
   289  // IsValidRelation determines if a string s is a valid relation. This means it does not contain any `:`, `#`, or spaces.
   290  func IsValidRelation(s string) bool {
   291  	return relationRegex.MatchString(s)
   292  }
   293  
   294  // IsValidUser determines if a string is a valid user. A valid user contains at most one `:`, at most one `#` and no spaces.
   295  func IsValidUser(user string) bool {
   296  	if strings.Count(user, ":") > 1 || strings.Count(user, "#") > 1 {
   297  		return false
   298  	}
   299  	if user == Wildcard || userIDRegex.MatchString(user) || objectRegex.MatchString(user) || userSetRegex.MatchString(user) {
   300  		return true
   301  	}
   302  
   303  	return false
   304  }
   305  
   306  // IsWildcard returns true if the string 's' could be interpreted as a typed or untyped wildcard (e.g. '*' or 'type:*').
   307  func IsWildcard(s string) bool {
   308  	return s == Wildcard || IsTypedWildcard(s)
   309  }
   310  
   311  // IsTypedWildcard returns true if the string 's' is a typed wildcard. A typed wildcard
   312  // has the form 'type:*'.
   313  func IsTypedWildcard(s string) bool {
   314  	if IsValidObject(s) {
   315  		_, id := SplitObject(s)
   316  		if id == Wildcard {
   317  			return true
   318  		}
   319  	}
   320  
   321  	return false
   322  }
   323  
   324  // TypedPublicWildcard returns the string tuple representation for a given object type (ex: "user:*").
   325  func TypedPublicWildcard(objectType string) string {
   326  	return BuildObject(objectType, Wildcard)
   327  }
   328  
   329  // MustParseTupleString attempts to parse a relationship tuple specified
   330  // in string notation and return the protobuf TupleKey for it. If parsing
   331  // of the string fails this  function will panic. It is meant for testing
   332  // purposes.
   333  //
   334  // Given string 'document:1#viewer@user:jon', return the protobuf TupleKey
   335  // for it.
   336  func MustParseTupleString(s string) *openfgav1.TupleKey {
   337  	t, err := ParseTupleString(s)
   338  	if err != nil {
   339  		panic(err)
   340  	}
   341  
   342  	return t
   343  }
   344  
   345  func MustParseTupleStrings(tupleStrs ...string) []*openfgav1.TupleKey {
   346  	tuples := make([]*openfgav1.TupleKey, 0, len(tupleStrs))
   347  	for _, tupleStr := range tupleStrs {
   348  		tuples = append(tuples, MustParseTupleString(tupleStr))
   349  	}
   350  
   351  	return tuples
   352  }
   353  
   354  // ParseTupleString attempts to parse a relationship tuple specified
   355  // in string notation and return the protobuf TupleKey for it. If parsing
   356  // of the string fails this  function returns an err.
   357  //
   358  // Given string 'document:1#viewer@user:jon', return the protobuf TupleKey
   359  // for it or an error.
   360  func ParseTupleString(s string) (*openfgav1.TupleKey, error) {
   361  	object, rhs, found := strings.Cut(s, "#")
   362  	if !found {
   363  		return nil, fmt.Errorf("expected at least one '#' separating the object and relation")
   364  	}
   365  
   366  	if !IsValidObject(object) {
   367  		return nil, fmt.Errorf("invalid tuple 'object' field format")
   368  	}
   369  
   370  	relation, user, found := strings.Cut(rhs, "@")
   371  	if !found {
   372  		return nil, fmt.Errorf("expected at least one '@' separating the relation and user")
   373  	}
   374  
   375  	if !IsValidRelation(relation) {
   376  		return nil, fmt.Errorf("invalid tuple 'relation' field format")
   377  	}
   378  
   379  	if !IsValidUser(user) {
   380  		return nil, fmt.Errorf("invalid tuple 'user' field format")
   381  	}
   382  
   383  	return &openfgav1.TupleKey{
   384  		Object:   object,
   385  		Relation: relation,
   386  		User:     user,
   387  	}, nil
   388  }