github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/developmentmembership/membership_test.go (about)

     1  package developmentmembership
     2  
     3  import (
     4  	"sort"
     5  	"testing"
     6  
     7  	"github.com/authzed/spicedb/internal/caveats"
     8  
     9  	"github.com/stretchr/testify/require"
    10  
    11  	core "github.com/authzed/spicedb/pkg/proto/core/v1"
    12  
    13  	"github.com/authzed/spicedb/pkg/graph"
    14  	"github.com/authzed/spicedb/pkg/testutil"
    15  	"github.com/authzed/spicedb/pkg/tuple"
    16  )
    17  
    18  var (
    19  	ONR      = tuple.ObjectAndRelation
    20  	Ellipsis = "..."
    21  )
    22  
    23  func DS(objectType string, objectID string, objectRelation string) *core.DirectSubject {
    24  	return &core.DirectSubject{
    25  		Subject: ONR(objectType, objectID, objectRelation),
    26  	}
    27  }
    28  
    29  func CaveatedDS(objectType string, objectID string, objectRelation string, caveatName string) *core.DirectSubject {
    30  	return &core.DirectSubject{
    31  		Subject:          ONR(objectType, objectID, objectRelation),
    32  		CaveatExpression: caveats.CaveatExprForTesting(caveatName),
    33  	}
    34  }
    35  
    36  var (
    37  	_this *core.ObjectAndRelation
    38  
    39  	companyOwner = graph.Leaf(ONR("folder", "company", "owner"),
    40  		(DS("user", "owner", Ellipsis)),
    41  	)
    42  	companyEditor = graph.Union(ONR("folder", "company", "editor"),
    43  		graph.Leaf(_this, (DS("user", "writer", Ellipsis))),
    44  		companyOwner,
    45  	)
    46  
    47  	auditorsOwner = graph.Leaf(ONR("folder", "auditors", "owner"))
    48  
    49  	auditorsEditor = graph.Union(ONR("folder", "auditors", "editor"),
    50  		graph.Leaf(_this),
    51  		auditorsOwner,
    52  	)
    53  
    54  	auditorsViewerRecursive = graph.Union(ONR("folder", "auditors", "viewer"),
    55  		graph.Leaf(_this,
    56  			(DS("user", "auditor", "...")),
    57  		),
    58  		auditorsEditor,
    59  		graph.Union(ONR("folder", "auditors", "viewer")),
    60  	)
    61  
    62  	companyViewerRecursive = graph.Union(ONR("folder", "company", "viewer"),
    63  		graph.Union(ONR("folder", "company", "viewer"),
    64  			auditorsViewerRecursive,
    65  			graph.Leaf(_this,
    66  				(DS("user", "legal", "...")),
    67  				(DS("folder", "auditors", "viewer")),
    68  			),
    69  		),
    70  		companyEditor,
    71  		graph.Union(ONR("folder", "company", "viewer")),
    72  	)
    73  )
    74  
    75  func TestMembershipSetBasic(t *testing.T) {
    76  	require := require.New(t)
    77  	ms := NewMembershipSet()
    78  
    79  	// Add some expansion trees.
    80  	fso, ok, err := ms.AddExpansion(ONR("folder", "company", "owner"), companyOwner)
    81  	require.True(ok)
    82  	require.NoError(err)
    83  	verifySubjects(t, require, fso, "user:owner")
    84  
    85  	fse, ok, err := ms.AddExpansion(ONR("folder", "company", "editor"), companyEditor)
    86  	require.True(ok)
    87  	require.NoError(err)
    88  	verifySubjects(t, require, fse, "user:owner", "user:writer")
    89  
    90  	fsv, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), companyViewerRecursive)
    91  	require.True(ok)
    92  	require.NoError(err)
    93  	verifySubjects(t, require, fsv, "folder:auditors#viewer", "user:auditor", "user:legal", "user:owner", "user:writer")
    94  }
    95  
    96  func TestMembershipSetIntersectionBasic(t *testing.T) {
    97  	require := require.New(t)
    98  	ms := NewMembershipSet()
    99  
   100  	intersection := graph.Intersection(ONR("folder", "company", "viewer"),
   101  		graph.Leaf(_this,
   102  			(DS("user", "legal", "...")),
   103  		),
   104  		graph.Leaf(_this,
   105  			(DS("user", "owner", "...")),
   106  			(DS("user", "legal", "...")),
   107  		),
   108  	)
   109  
   110  	fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection)
   111  	require.True(ok)
   112  	require.NoError(err)
   113  	verifySubjects(t, require, fso, "user:legal")
   114  }
   115  
   116  func TestMembershipSetIntersectionWithDifferentTypesOneMissingLeft(t *testing.T) {
   117  	require := require.New(t)
   118  	ms := NewMembershipSet()
   119  
   120  	intersection := graph.Intersection(ONR("folder", "company", "viewer"),
   121  		graph.Leaf(_this,
   122  			(DS("user", "legal", "...")),
   123  			(DS("folder", "foobar", "...")),
   124  		),
   125  		graph.Leaf(_this,
   126  			(DS("user", "owner", "...")),
   127  			(DS("user", "legal", "...")),
   128  		),
   129  	)
   130  
   131  	fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection)
   132  	require.True(ok)
   133  	require.NoError(err)
   134  	verifySubjects(t, require, fso, "user:legal")
   135  }
   136  
   137  func TestMembershipSetIntersectionWithDifferentTypesOneMissingRight(t *testing.T) {
   138  	require := require.New(t)
   139  	ms := NewMembershipSet()
   140  
   141  	intersection := graph.Intersection(ONR("folder", "company", "viewer"),
   142  		graph.Leaf(_this,
   143  			(DS("user", "legal", "...")),
   144  		),
   145  		graph.Leaf(_this,
   146  			(DS("user", "owner", "...")),
   147  			(DS("user", "legal", "...")),
   148  			(DS("folder", "foobar", "...")),
   149  		),
   150  	)
   151  
   152  	fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection)
   153  	require.True(ok)
   154  	require.NoError(err)
   155  	verifySubjects(t, require, fso, "user:legal")
   156  }
   157  
   158  func TestMembershipSetIntersectionWithDifferentTypes(t *testing.T) {
   159  	require := require.New(t)
   160  	ms := NewMembershipSet()
   161  
   162  	intersection := graph.Intersection(ONR("folder", "company", "viewer"),
   163  		graph.Leaf(_this,
   164  			(DS("user", "legal", "...")),
   165  			(DS("folder", "foobar", "...")),
   166  			(DS("folder", "barbaz", "...")),
   167  		),
   168  		graph.Leaf(_this,
   169  			(DS("user", "owner", "...")),
   170  			(DS("user", "legal", "...")),
   171  			(DS("folder", "barbaz", "...")),
   172  		),
   173  	)
   174  
   175  	fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection)
   176  	require.True(ok)
   177  	require.NoError(err)
   178  	verifySubjects(t, require, fso, "folder:barbaz", "user:legal")
   179  }
   180  
   181  func TestMembershipSetExclusion(t *testing.T) {
   182  	require := require.New(t)
   183  	ms := NewMembershipSet()
   184  
   185  	exclusion := graph.Exclusion(ONR("folder", "company", "viewer"),
   186  		graph.Leaf(_this,
   187  			(DS("user", "owner", "...")),
   188  			(DS("user", "legal", "...")),
   189  		),
   190  		graph.Leaf(_this,
   191  			(DS("user", "legal", "...")),
   192  		),
   193  	)
   194  
   195  	fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), exclusion)
   196  	require.True(ok)
   197  	require.NoError(err)
   198  	verifySubjects(t, require, fso, "user:owner")
   199  }
   200  
   201  func TestMembershipSetExclusionMultiple(t *testing.T) {
   202  	require := require.New(t)
   203  	ms := NewMembershipSet()
   204  
   205  	exclusion := graph.Exclusion(ONR("folder", "company", "viewer"),
   206  		graph.Leaf(_this,
   207  			(DS("user", "owner", "...")),
   208  			(DS("user", "legal", "...")),
   209  			(DS("user", "third", "...")),
   210  		),
   211  		graph.Leaf(_this,
   212  			(DS("user", "legal", "...")),
   213  		),
   214  		graph.Leaf(_this,
   215  			(DS("user", "owner", "...")),
   216  		),
   217  	)
   218  
   219  	fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), exclusion)
   220  	require.True(ok)
   221  	require.NoError(err)
   222  	verifySubjects(t, require, fso, "user:third")
   223  }
   224  
   225  func TestMembershipSetExclusionMultipleWithWildcard(t *testing.T) {
   226  	require := require.New(t)
   227  	ms := NewMembershipSet()
   228  
   229  	exclusion := graph.Exclusion(ONR("folder", "company", "viewer"),
   230  		graph.Leaf(_this,
   231  			(DS("user", "owner", "...")),
   232  			(DS("user", "legal", "...")),
   233  		),
   234  		graph.Leaf(_this,
   235  			(DS("user", "legal", "...")),
   236  		),
   237  		graph.Leaf(_this,
   238  			(DS("user", "*", "...")),
   239  		),
   240  	)
   241  
   242  	fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), exclusion)
   243  	require.True(ok)
   244  	require.NoError(err)
   245  	verifySubjects(t, require, fso)
   246  }
   247  
   248  func TestMembershipSetExclusionMultipleMiddle(t *testing.T) {
   249  	require := require.New(t)
   250  	ms := NewMembershipSet()
   251  
   252  	exclusion := graph.Exclusion(ONR("folder", "company", "viewer"),
   253  		graph.Leaf(_this,
   254  			(DS("user", "owner", "...")),
   255  			(DS("user", "legal", "...")),
   256  			(DS("user", "third", "...")),
   257  		),
   258  		graph.Leaf(_this,
   259  			(DS("user", "another", "...")),
   260  		),
   261  		graph.Leaf(_this,
   262  			(DS("user", "owner", "...")),
   263  		),
   264  	)
   265  
   266  	fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), exclusion)
   267  	require.True(ok)
   268  	require.NoError(err)
   269  	verifySubjects(t, require, fso, "user:third", "user:legal")
   270  }
   271  
   272  func TestMembershipSetIntersectionWithOneWildcard(t *testing.T) {
   273  	require := require.New(t)
   274  	ms := NewMembershipSet()
   275  
   276  	intersection := graph.Intersection(ONR("folder", "company", "viewer"),
   277  		graph.Leaf(_this,
   278  			(DS("user", "owner", "...")),
   279  			(DS("user", "*", "...")),
   280  		),
   281  		graph.Leaf(_this,
   282  			(DS("user", "legal", "...")),
   283  		),
   284  	)
   285  
   286  	fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection)
   287  	require.True(ok)
   288  	require.NoError(err)
   289  	verifySubjects(t, require, fso, "user:legal")
   290  }
   291  
   292  func TestMembershipSetIntersectionWithAllWildcardLeft(t *testing.T) {
   293  	require := require.New(t)
   294  	ms := NewMembershipSet()
   295  
   296  	intersection := graph.Intersection(ONR("folder", "company", "viewer"),
   297  		graph.Leaf(_this,
   298  			(DS("user", "owner", "...")),
   299  			(DS("user", "*", "...")),
   300  		),
   301  		graph.Leaf(_this,
   302  			(DS("user", "*", "...")),
   303  		),
   304  	)
   305  
   306  	fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection)
   307  	require.True(ok)
   308  	require.NoError(err)
   309  	verifySubjects(t, require, fso, "user:*", "user:owner")
   310  }
   311  
   312  func TestMembershipSetIntersectionWithAllWildcardRight(t *testing.T) {
   313  	require := require.New(t)
   314  	ms := NewMembershipSet()
   315  
   316  	intersection := graph.Intersection(ONR("folder", "company", "viewer"),
   317  		graph.Leaf(_this,
   318  			(DS("user", "*", "...")),
   319  		),
   320  		graph.Leaf(_this,
   321  			(DS("user", "owner", "...")),
   322  			(DS("user", "*", "...")),
   323  		),
   324  	)
   325  
   326  	fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection)
   327  	require.True(ok)
   328  	require.NoError(err)
   329  	verifySubjects(t, require, fso, "user:*", "user:owner")
   330  }
   331  
   332  func TestMembershipSetExclusionWithLeftWildcard(t *testing.T) {
   333  	require := require.New(t)
   334  	ms := NewMembershipSet()
   335  
   336  	exclusion := graph.Exclusion(ONR("folder", "company", "viewer"),
   337  		graph.Leaf(_this,
   338  			(DS("user", "owner", "...")),
   339  			(DS("user", "*", "...")),
   340  		),
   341  		graph.Leaf(_this,
   342  			(DS("user", "legal", "...")),
   343  		),
   344  	)
   345  
   346  	fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), exclusion)
   347  	require.True(ok)
   348  	require.NoError(err)
   349  	verifySubjects(t, require, fso, "user:*", "user:owner")
   350  }
   351  
   352  func TestMembershipSetExclusionWithRightWildcard(t *testing.T) {
   353  	require := require.New(t)
   354  	ms := NewMembershipSet()
   355  
   356  	exclusion := graph.Exclusion(ONR("folder", "company", "viewer"),
   357  		graph.Leaf(_this,
   358  			(DS("user", "owner", "...")),
   359  			(DS("user", "legal", "...")),
   360  		),
   361  		graph.Leaf(_this,
   362  			(DS("user", "*", "...")),
   363  		),
   364  	)
   365  
   366  	fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), exclusion)
   367  	require.True(ok)
   368  	require.NoError(err)
   369  	verifySubjects(t, require, fso)
   370  }
   371  
   372  func TestMembershipSetIntersectionWithThreeWildcards(t *testing.T) {
   373  	require := require.New(t)
   374  	ms := NewMembershipSet()
   375  
   376  	intersection := graph.Intersection(ONR("folder", "company", "viewer"),
   377  		graph.Leaf(_this,
   378  			(DS("user", "owner", "...")),
   379  			(DS("user", "legal", "...")),
   380  		),
   381  		graph.Leaf(_this,
   382  			(DS("user", "*", "...")),
   383  		),
   384  		graph.Leaf(_this,
   385  			(DS("user", "*", "...")),
   386  		),
   387  	)
   388  
   389  	fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection)
   390  	require.True(ok)
   391  	require.NoError(err)
   392  	verifySubjects(t, require, fso, "user:owner", "user:legal")
   393  }
   394  
   395  func TestMembershipSetIntersectionWithOneBranchMissingWildcards(t *testing.T) {
   396  	require := require.New(t)
   397  	ms := NewMembershipSet()
   398  
   399  	intersection := graph.Intersection(ONR("folder", "company", "viewer"),
   400  		graph.Leaf(_this,
   401  			(DS("user", "owner", "...")),
   402  			(DS("user", "legal", "...")),
   403  			(DS("user", "*", "...")),
   404  		),
   405  		graph.Leaf(_this,
   406  			(DS("user", "owner", "...")),
   407  		),
   408  		graph.Leaf(_this,
   409  			(DS("user", "*", "...")),
   410  		),
   411  	)
   412  
   413  	fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection)
   414  	require.True(ok)
   415  	require.NoError(err)
   416  	verifySubjects(t, require, fso, "user:owner")
   417  }
   418  
   419  func TestMembershipSetIntersectionWithTwoBranchesMissingWildcards(t *testing.T) {
   420  	require := require.New(t)
   421  	ms := NewMembershipSet()
   422  
   423  	intersection := graph.Intersection(ONR("folder", "company", "viewer"),
   424  		graph.Leaf(_this,
   425  			(DS("user", "owner", "...")),
   426  			(DS("user", "legal", "...")),
   427  		),
   428  		graph.Leaf(_this,
   429  			(DS("user", "another", "...")),
   430  		),
   431  		graph.Leaf(_this,
   432  			(DS("user", "*", "...")),
   433  		),
   434  	)
   435  
   436  	fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection)
   437  	require.True(ok)
   438  	require.NoError(err)
   439  	verifySubjects(t, require, fso)
   440  }
   441  
   442  func TestMembershipSetWithCaveats(t *testing.T) {
   443  	require := require.New(t)
   444  	ms := NewMembershipSet()
   445  
   446  	intersection := graph.Intersection(ONR("folder", "company", "viewer"),
   447  		graph.Leaf(_this,
   448  			(DS("user", "owner", "...")),
   449  			(DS("user", "legal", "...")),
   450  			(DS("user", "*", "...")),
   451  		),
   452  		graph.Leaf(_this,
   453  			(CaveatedDS("user", "owner", "...", "somecaveat")),
   454  		),
   455  		graph.Leaf(_this,
   456  			(DS("user", "*", "...")),
   457  		),
   458  	)
   459  	intersection.CaveatExpression = caveats.CaveatExprForTesting("anothercaveat")
   460  
   461  	fso, ok, err := ms.AddExpansion(ONR("folder", "company", "viewer"), intersection)
   462  	require.True(ok)
   463  	require.NoError(err)
   464  	verifySubjects(t, require, fso, "user:owner")
   465  
   466  	// Verify the caveat on the user:owner.
   467  	subject, ok := fso.LookupSubject(ONR("user", "owner", "..."))
   468  	require.True(ok)
   469  
   470  	testutil.RequireProtoEqual(t, subject.GetCaveatExpression(), caveats.And(
   471  		caveats.CaveatExprForTesting("anothercaveat"),
   472  		caveats.CaveatExprForTesting("somecaveat"),
   473  	), "found invalid caveat expr for subject")
   474  }
   475  
   476  func verifySubjects(t *testing.T, require *require.Assertions, fs FoundSubjects, expected ...string) {
   477  	foundSubjects := []*core.ObjectAndRelation{}
   478  	for _, found := range fs.ListFound() {
   479  		foundSubjects = append(foundSubjects, found.Subject())
   480  
   481  		_, ok := fs.LookupSubject(found.Subject())
   482  		require.True(ok, "Could not find expected subject %s", found.Subject())
   483  	}
   484  
   485  	found := tuple.StringsONRs(foundSubjects)
   486  	sort.Strings(expected)
   487  	sort.Strings(found)
   488  
   489  	testutil.RequireEqualEmptyNil(t, expected, found)
   490  }