diff --git a/auth/auth_test.go b/auth/auth_test.go index c0b334cfab..c062a74b5b 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -32,13 +32,18 @@ func NewTestAuthenticator(t testing.TB, dataStore sgbucket.DataStore, channelCom return NewAuthenticator(dataStore, channelComputer, opts) } -func canSeeAllChannels(princ Principal, channels base.Set) bool { - for channel := range channels { - if !princ.canSeeChannel(channel) { - return false - } +// requireCanSeeChannels asserts that the given principal can see all of the specified channels. +func requireCanSeeChannels(t *testing.T, princ Principal, channels ...string) { + for _, channel := range channels { + require.True(t, princ.canSeeChannel(channel), "Expected %s to be able to see channel %q", princ.Name(), channel) + } +} + +// assertCannotSeeChannels asserts that the given principal cannot see any of the specified channels. +func requireCannotSeeChannels(t *testing.T, princ Principal, channels ...string) { + for _, channel := range channels { + require.False(t, princ.canSeeChannel(channel), "Expected %s to NOT be able to see channel %q", princ.Name(), channel) } - return true } func TestValidateGuestUser(t *testing.T) { @@ -49,8 +54,8 @@ func TestValidateGuestUser(t *testing.T) { auth := NewTestAuthenticator(t, dataStore, nil, DefaultAuthenticatorOptions(ctx)) user, err := auth.NewUser("", "", nil) - assert.True(t, user != nil) - assert.True(t, err == nil) + require.NoError(t, err) + require.NotNil(t, user) } func TestValidateUser(t *testing.T) { @@ -104,15 +109,16 @@ func TestValidateUserEmail(t *testing.T) { auth := NewTestAuthenticator(t, dataStore, nil, DefaultAuthenticatorOptions(ctx)) badEmails := []string{"", "foo", "foo@", "@bar", "foo@bar@buzz"} for _, e := range badEmails { - assert.False(t, IsValidEmail(e)) + assert.False(t, IsValidEmail(e), "Shouldn't validate as email: %q", e) } goodEmails := []string{"foo@bar", "foo.99@bar.com", "f@bar.exampl-3.com."} for _, e := range goodEmails { - assert.True(t, IsValidEmail(e)) + assert.True(t, IsValidEmail(e), "Should validate as email: %q", e) } - user, _ := auth.NewUser("ValidName", "letmein", nil) - assert.False(t, user.SetEmail("foo") == nil) - assert.Equal(t, nil, user.SetEmail("foo@example.com")) + user, err := auth.NewUser("ValidName", "letmein", nil) + require.NoError(t, err) + require.Error(t, user.SetEmail("foo")) + require.NoError(t, user.SetEmail("foo@example.com")) } func TestUserPasswords(t *testing.T) { @@ -157,8 +163,7 @@ func TestSerializeUser(t *testing.T) { log.Printf("Marshaled User as: %s", encoded) resu := &userImpl{auth: auth} - err := base.JSONUnmarshal(encoded, resu) - assert.True(t, err == nil) + require.NoError(t, base.JSONUnmarshal(encoded, resu)) assert.Equal(t, user.Name(), resu.Name()) assert.Equal(t, user.Email(), resu.Email()) assert.Equal(t, user.ExplicitChannels(), resu.ExplicitChannels()) @@ -178,9 +183,8 @@ func TestSerializeRole(t *testing.T) { require.NotNil(t, encoded) log.Printf("Marshaled Role as: %s", encoded) elor := &roleImpl{} - err := base.JSONUnmarshal(encoded, elor) + require.NoError(t, base.JSONUnmarshal(encoded, elor)) - assert.True(t, err == nil) assert.Equal(t, role.Name(), elor.Name()) assert.Equal(t, role.ExplicitChannels(), elor.ExplicitChannels()) } @@ -193,70 +197,57 @@ func TestUserAccess(t *testing.T) { defer bucket.Close(ctx) dataStore := bucket.GetSingleDataStore() auth := NewTestAuthenticator(t, dataStore, nil, DefaultAuthenticatorOptions(ctx)) - user, _ := auth.NewUser("foo", "password", nil) - assert.Equal(t, ch.BaseSetOf(t, "!"), user.expandWildCardChannel(ch.BaseSetOf(t, "*"))) - assert.False(t, user.canSeeChannel("x")) - assert.True(t, canSeeAllChannels(user, ch.BaseSetOf(t))) - assert.False(t, canSeeAllChannels(user, ch.BaseSetOf(t, "x"))) - assert.False(t, canSeeAllChannels(user, ch.BaseSetOf(t, "x", "y"))) - assert.False(t, canSeeAllChannels(user, ch.BaseSetOf(t, "*"))) - assert.False(t, user.authorizeAllChannels(ch.BaseSetOf(t, "*")) == nil) - assert.False(t, user.authorizeAnyChannel(ch.BaseSetOf(t, "x", "y")) == nil) - assert.False(t, user.authorizeAnyChannel(ch.BaseSetOf(t)) == nil) + user, err := auth.NewUser("foo", "password", nil) + require.NoError(t, err) + requireExpandWildCardChannel(t, user, []string{"!"}, []string{"*"}) + requireCannotSeeChannels(t, user, "x", "y", "*") + require.ErrorIs(t, user.authorizeAllChannels(ch.BaseSetOf(t, "*")), errNotAllowedChannels) + require.ErrorIs(t, user.authorizeAnyChannel(ch.BaseSetOf(t, "x", "y")), errUnauthorized) + require.ErrorIs(t, user.authorizeAnyChannel(ch.BaseSetOf(t)), errUnauthorized) // User with access to one channel: user.setChannels(ch.AtSequence(ch.BaseSetOf(t, "x"), 1)) - assert.Equal(t, ch.BaseSetOf(t, "x"), user.expandWildCardChannel(ch.BaseSetOf(t, "*"))) - assert.True(t, canSeeAllChannels(user, ch.BaseSetOf(t))) - assert.True(t, canSeeAllChannels(user, ch.BaseSetOf(t, "x"))) - assert.False(t, canSeeAllChannels(user, ch.BaseSetOf(t, "x", "y"))) - assert.False(t, user.authorizeAllChannels(ch.BaseSetOf(t, "x", "y")) == nil) - assert.False(t, user.authorizeAllChannels(ch.BaseSetOf(t, "*")) == nil) - assert.True(t, user.authorizeAnyChannel(ch.BaseSetOf(t, "x", "y")) == nil) - assert.False(t, user.authorizeAnyChannel(ch.BaseSetOf(t, "y")) == nil) - assert.False(t, user.authorizeAnyChannel(ch.BaseSetOf(t)) == nil) + requireExpandWildCardChannel(t, user, []string{"x"}, []string{"*"}) + requireCanSeeChannels(t, user, "x") + requireCannotSeeChannels(t, user, "y", "*") + require.NoError(t, user.authorizeAnyChannel(ch.BaseSetOf(t, "x", "y"))) + require.ErrorIs(t, user.authorizeAnyChannel(ch.BaseSetOf(t, "y")), errUnauthorized) + require.ErrorIs(t, user.authorizeAnyChannel(ch.BaseSetOf(t)), errUnauthorized) // User with access to one channel and one derived channel: user.setChannels(ch.AtSequence(ch.BaseSetOf(t, "x", "z"), 1)) - assert.Equal(t, ch.BaseSetOf(t, "x", "z"), user.expandWildCardChannel(ch.BaseSetOf(t, "*"))) - assert.Equal(t, ch.BaseSetOf(t, "x"), user.expandWildCardChannel(ch.BaseSetOf(t, "x"))) - assert.True(t, canSeeAllChannels(user, ch.BaseSetOf(t))) - assert.True(t, canSeeAllChannels(user, ch.BaseSetOf(t, "x"))) - assert.False(t, canSeeAllChannels(user, ch.BaseSetOf(t, "x", "y"))) - assert.False(t, user.authorizeAllChannels(ch.BaseSetOf(t, "x", "y")) == nil) - assert.False(t, user.authorizeAllChannels(ch.BaseSetOf(t, "*")) == nil) + requireExpandWildCardChannel(t, user, []string{"x", "z"}, []string{"*"}) + requireExpandWildCardChannel(t, user, []string{"x"}, []string{"x"}) + requireCanSeeChannels(t, user, "x", "z") + requireCannotSeeChannels(t, user, "y", "*") + require.ErrorIs(t, user.authorizeAllChannels(ch.BaseSetOf(t, "x", "y")), errNotAllowedChannels) + require.ErrorIs(t, user.authorizeAllChannels(ch.BaseSetOf(t, "*")), errNotAllowedChannels) // User with access to two channels: user.setChannels(ch.AtSequence(ch.BaseSetOf(t, "x", "z"), 1)) - assert.Equal(t, ch.BaseSetOf(t, "x", "z"), user.expandWildCardChannel(ch.BaseSetOf(t, "*"))) - assert.Equal(t, ch.BaseSetOf(t, "x"), user.expandWildCardChannel(ch.BaseSetOf(t, "x"))) - assert.True(t, canSeeAllChannels(user, ch.BaseSetOf(t))) - assert.True(t, canSeeAllChannels(user, ch.BaseSetOf(t, "x"))) - assert.False(t, canSeeAllChannels(user, ch.BaseSetOf(t, "x", "y"))) - assert.False(t, user.authorizeAllChannels(ch.BaseSetOf(t, "x", "y")) == nil) - assert.False(t, user.authorizeAllChannels(ch.BaseSetOf(t, "*")) == nil) + requireExpandWildCardChannel(t, user, []string{"x", "z"}, []string{"*"}) + requireExpandWildCardChannel(t, user, []string{"x"}, []string{"x"}) + requireCanSeeChannels(t, user, "x", "z") + requireCannotSeeChannels(t, user, "y", "*") + require.ErrorIs(t, user.authorizeAllChannels(ch.BaseSetOf(t, "x", "y")), errNotAllowedChannels) + require.ErrorIs(t, user.authorizeAllChannels(ch.BaseSetOf(t, "*")), errNotAllowedChannels) user.setChannels(ch.AtSequence(ch.BaseSetOf(t, "x", "y"), 1)) - assert.Equal(t, ch.BaseSetOf(t, "x", "y"), user.expandWildCardChannel(ch.BaseSetOf(t, "*"))) - assert.True(t, canSeeAllChannels(user, ch.BaseSetOf(t))) - assert.True(t, canSeeAllChannels(user, ch.BaseSetOf(t, "x"))) - assert.True(t, canSeeAllChannels(user, ch.BaseSetOf(t, "x", "y"))) - assert.False(t, canSeeAllChannels(user, ch.BaseSetOf(t, "x", "y", "z"))) - assert.True(t, user.authorizeAllChannels(ch.BaseSetOf(t, "x", "y")) == nil) - assert.False(t, user.authorizeAllChannels(ch.BaseSetOf(t, "*")) == nil) + requireExpandWildCardChannel(t, user, []string{"x", "y"}, []string{"*"}) + requireCanSeeChannels(t, user, "x", "y") + requireCannotSeeChannels(t, user, "z", "*") + require.NoError(t, user.authorizeAllChannels(ch.BaseSetOf(t, "x", "y"))) + require.ErrorIs(t, user.authorizeAllChannels(ch.BaseSetOf(t, "*")), errNotAllowedChannels) // User with wildcard access: user.setChannels(ch.AtSequence(ch.BaseSetOf(t, "*", "q"), 1)) - assert.Equal(t, ch.BaseSetOf(t, "*", "q"), user.expandWildCardChannel(ch.BaseSetOf(t, "*"))) - assert.True(t, user.canSeeChannel("*")) - assert.True(t, canSeeAllChannels(user, ch.BaseSetOf(t))) - assert.True(t, canSeeAllChannels(user, ch.BaseSetOf(t, "x"))) - assert.True(t, canSeeAllChannels(user, ch.BaseSetOf(t, "x", "y"))) - assert.True(t, user.authorizeAllChannels(ch.BaseSetOf(t, "x", "y")) == nil) - assert.True(t, user.authorizeAllChannels(ch.BaseSetOf(t, "*")) == nil) - assert.True(t, user.authorizeAnyChannel(ch.BaseSetOf(t, "x")) == nil) - assert.True(t, user.authorizeAnyChannel(ch.BaseSetOf(t, "*")) == nil) - assert.True(t, user.authorizeAnyChannel(ch.BaseSetOf(t)) == nil) + requireExpandWildCardChannel(t, user, []string{"*", "q"}, []string{"*"}) + requireCanSeeChannels(t, user, "*", "q", "x", "y") + require.NoError(t, user.authorizeAllChannels(ch.BaseSetOf(t, "x", "y"))) + require.NoError(t, user.authorizeAllChannels(ch.BaseSetOf(t, "*"))) + require.NoError(t, user.authorizeAnyChannel(ch.BaseSetOf(t, "x"))) + require.NoError(t, user.authorizeAnyChannel(ch.BaseSetOf(t, "*"))) + require.NoError(t, user.authorizeAnyChannel(ch.BaseSetOf(t))) } func TestGetMissingUser(t *testing.T) { @@ -267,11 +258,11 @@ func TestGetMissingUser(t *testing.T) { dataStore := bucket.GetSingleDataStore() auth := NewTestAuthenticator(t, dataStore, nil, DefaultAuthenticatorOptions(ctx)) user, err := auth.GetUser("noSuchUser") - assert.Equal(t, nil, err) - assert.True(t, user == nil) + require.NoError(t, err) + require.Nil(t, user) user, err = auth.GetUserByEmail("noreply@example.com") - assert.Equal(t, nil, err) - assert.True(t, user == nil) + require.NoError(t, err) + require.Nil(t, user) } func TestGetMissingRole(t *testing.T) { @@ -282,8 +273,8 @@ func TestGetMissingRole(t *testing.T) { dataStore := bucket.GetSingleDataStore() auth := NewTestAuthenticator(t, dataStore, nil, DefaultAuthenticatorOptions(ctx)) role, err := auth.GetRole("noSuchRole") - assert.Equal(t, nil, err) - assert.True(t, role == nil) + require.NoError(t, err) + require.Nil(t, role) } func TestGetGuestUser(t *testing.T) { @@ -293,7 +284,8 @@ func TestGetGuestUser(t *testing.T) { dataStore := bucket.GetSingleDataStore() auth := NewTestAuthenticator(t, dataStore, nil, DefaultAuthenticatorOptions(ctx)) user, err := auth.GetUser("") - require.Equal(t, nil, err) + require.NoError(t, err) + require.NotNil(t, user) assert.Equal(t, auth.defaultGuestUser(), user) } @@ -447,10 +439,10 @@ func TestRebuildUserChannelsNamedCollection(t *testing.T) { "scope1": {"collection1": struct{}{}}, } auth := NewTestAuthenticator(t, dataStore, &computer, options) - user, _ := auth.NewUser("testUser", "password", nil) + user, err := auth.NewUser("testUser", "password", nil) + require.NoError(t, err) user.SetCollectionExplicitChannels("scope1", "collection1", ch.AtSequence(ch.BaseSetOf(t, "explicit2"), 1), 0) - err := auth.Save(user) - assert.NoError(t, err) + require.NoError(t, auth.Save(user)) err = auth.InvalidateChannels("testUser", true, base.ScopeAndCollectionNames{base.NewScopeAndCollectionName("scope1", "collection1")}, 2) assert.NoError(t, err) @@ -481,10 +473,10 @@ func TestRebuildRoleChannels(t *testing.T) { assert.NoError(t, err) err = auth.InvalidateDefaultChannels("testRole", false, 1) - assert.Equal(t, nil, err) + require.NoError(t, err) role2, err := auth.GetRole("testRole") - assert.Equal(t, nil, err) + require.NoError(t, err) assert.Equal(t, ch.AtSequence(ch.BaseSetOf(t, "explicit1", "derived1", "derived2", "!"), 1), role2.Channels()) } @@ -496,9 +488,9 @@ func TestRebuildChannelsError(t *testing.T) { computer := mockComputer{} auth := NewTestAuthenticator(t, dataStore, &computer, DefaultAuthenticatorOptions(base.TestCtx(t))) role, err := auth.NewRole("testRole2", ch.BaseSetOf(t, "explicit1")) - assert.NoError(t, err) + require.NoError(t, err) err = auth.Save(role) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, nil, auth.InvalidateDefaultChannels("testRole2", false, 1)) @@ -506,7 +498,7 @@ func TestRebuildChannelsError(t *testing.T) { role2, err := auth.GetRole("testRole2") assert.Nil(t, role2) - assert.Equal(t, computer.err, err) + require.ErrorIs(t, err, computer.err) } func TestRebuildUserRoles(t *testing.T) { @@ -516,10 +508,11 @@ func TestRebuildUserRoles(t *testing.T) { dataStore := bucket.GetSingleDataStore() computer := mockComputer{roles: ch.AtSequence(base.SetOf("role1", "role2"), 3)} auth := NewTestAuthenticator(t, dataStore, &computer, DefaultAuthenticatorOptions(base.TestCtx(t))) - user, _ := auth.NewUser("testUser", "letmein", nil) + user, err := auth.NewUser("testUser", "letmein", nil) + require.NoError(t, err) user.SetExplicitRoles(ch.TimedSet{"role3": ch.NewVbSimpleSequence(1), "role1": ch.NewVbSimpleSequence(1)}, 1) - err := auth.Save(user) - assert.Equal(t, nil, err) + err = auth.Save(user) + require.NoError(t, err) // Retrieve the user, triggers initial build of roles user1, err := auth.GetUser("testUser") @@ -530,7 +523,7 @@ func TestRebuildUserRoles(t *testing.T) { // Invalidate the roles, triggers rebuild err = auth.InvalidateRoles("testUser", 1) - assert.Equal(t, nil, err) + require.NoError(t, err) user2, err := auth.GetUser("testUser") assert.Equal(t, nil, err) @@ -546,26 +539,30 @@ func TestRoleInheritance(t *testing.T) { defer bucket.Close(ctx) dataStore := bucket.GetSingleDataStore() auth := NewTestAuthenticator(t, dataStore, nil, DefaultAuthenticatorOptions(base.TestCtx(t))) - role, _ := auth.NewRole("square", ch.BaseSetOf(t, "dull", "duller", "dullest")) - assert.Equal(t, nil, auth.Save(role)) - role, _ = auth.NewRole("frood", ch.BaseSetOf(t, "hoopy", "hoopier", "hoopiest")) + role, err := auth.NewRole("square", ch.BaseSetOf(t, "dull", "duller", "dullest")) + require.NoError(t, err) + require.NoError(t, auth.Save(role)) + role, err = auth.NewRole("frood", ch.BaseSetOf(t, "hoopy", "hoopier", "hoopiest")) + require.NoError(t, err) assert.Equal(t, nil, auth.Save(role)) - user, _ := auth.NewUser("arthur", "password", ch.BaseSetOf(t, "britain")) - user.(*userImpl).setRolesSince(ch.TimedSet{"square": ch.NewVbSimpleSequence(0x3), "nonexistent": ch.NewVbSimpleSequence(0x42), "frood": ch.NewVbSimpleSequence(0x4)}) + user, err := auth.NewUser("arthur", "password", ch.BaseSetOf(t, "britain")) + require.NoError(t, err) + userImpl, ok := user.(*userImpl) + require.True(t, ok) + // Directly set the roles, bypassing the usual SetRoles method, so we can set the "since" value: + userImpl.setRolesSince(ch.TimedSet{"square": ch.NewVbSimpleSequence(0x3), "nonexistent": ch.NewVbSimpleSequence(0x42), "frood": ch.NewVbSimpleSequence(0x4)}) assert.Equal(t, ch.TimedSet{"square": ch.NewVbSimpleSequence(0x3), "nonexistent": ch.NewVbSimpleSequence(0x42), "frood": ch.NewVbSimpleSequence(0x4)}, user.RoleNames()) require.NoError(t, auth.Save(user)) user2, err := auth.GetUser("arthur") - assert.Equal(t, nil, err) + require.NoError(t, err) log.Printf("Channels = %s", user2.Channels()) assert.Equal(t, ch.AtSequence(ch.BaseSetOf(t, "!", "britain"), 1), user2.Channels()) assert.Equal(t, ch.TimedSet{"!": ch.NewVbSimpleSequence(0x1), "britain": ch.NewVbSimpleSequence(0x1), "dull": ch.NewVbSimpleSequence(0x3), "duller": ch.NewVbSimpleSequence(0x3), "dullest": ch.NewVbSimpleSequence(0x3), "hoopy": ch.NewVbSimpleSequence(0x4), "hoopier": ch.NewVbSimpleSequence(0x4), "hoopiest": ch.NewVbSimpleSequence(0x4)}, user2.inheritedChannels()) - assert.True(t, user2.canSeeChannel("britain")) - assert.True(t, user2.canSeeChannel("duller")) - assert.True(t, user2.canSeeChannel("hoopy")) - assert.Equal(t, nil, user2.authorizeAllChannels(ch.BaseSetOf(t, "britain", "dull", "hoopiest"))) + requireCanSeeChannels(t, user2, "britain", "duller", "hoopy") + require.NoError(t, user2.authorizeAllChannels(ch.BaseSetOf(t, "britain", "dull", "hoopiest"))) } func TestRegisterUser(t *testing.T) { @@ -600,7 +597,7 @@ func TestRegisterUser(t *testing.T) { user, err = auth.GetUser("UnknownName") require.NoError(t, err) - assert.Equal(t, nil, user) + require.Nil(t, user) user, err = auth.GetUserByEmail("bar@example.com") require.NoError(t, err) @@ -619,7 +616,7 @@ func TestRegisterUser(t *testing.T) { // Make sure we can't retrieve 01234567890 by supplying empty email. user, err = auth.GetUserByEmail("") require.NoError(t, err) - assert.Equal(t, nil, user) + require.Nil(t, user) // Try to register a user based on invalid email user, err = auth.RegisterNewUser("foo", "bar") @@ -783,8 +780,10 @@ func TestConcurrentUserWrites(t *testing.T) { require.Len(t, user.RoleNames(), 2) // Check the password hash bcrypt cost - userImpl := user.(*userImpl) - cost, _ := bcrypt.Cost(userImpl.PasswordHash_) + userImpl, ok := user.(*userImpl) + require.True(t, ok) + cost, err := bcrypt.Cost(userImpl.PasswordHash_) + require.NoError(t, err) assert.Equal(t, cost, DefaultBcryptCost) } @@ -1245,18 +1244,15 @@ func TestGetPrincipal(t *testing.T) { assert.NoError(t, err) assert.Equal(t, roleRoot, principal.Name()) assert.Equal(t, accessViewKeyRoot, principal.accessViewKey()) - assert.True(t, principal.canSeeChannel(channelRead)) - assert.True(t, principal.canSeeChannel(channelWrite)) - assert.True(t, principal.canSeeChannel(channelExecute)) + requireCanSeeChannels(t, principal, channelRead, channelWrite, channelExecute) // Get the principal against user role and verify the details. principal, err = auth.GetPrincipal(roleUser, false) assert.NoError(t, err) assert.Equal(t, roleUser, principal.Name()) assert.Equal(t, accessViewKeyUser, principal.accessViewKey()) - assert.True(t, principal.canSeeChannel(channelRead)) - assert.False(t, principal.canSeeChannel(channelWrite)) - assert.True(t, principal.canSeeChannel(channelExecute)) + requireCanSeeChannels(t, principal, channelRead, channelExecute) + requireCannotSeeChannels(t, principal, channelWrite) // Create a new user with new set of channels and assign user role to the user. user, err := auth.NewUser(username, password, ch.BaseSetOf( @@ -1270,12 +1266,8 @@ func TestGetPrincipal(t *testing.T) { assert.NoError(t, err) assert.Equal(t, username, principal.Name()) assert.Equal(t, username, principal.accessViewKey()) - assert.True(t, principal.canSeeChannel(channelRead)) - assert.False(t, principal.canSeeChannel(channelWrite)) - assert.True(t, principal.canSeeChannel(channelExecute)) - assert.True(t, principal.canSeeChannel(channelCreate)) - assert.True(t, principal.canSeeChannel(channelUpdate)) - assert.True(t, principal.canSeeChannel(channelDelete)) + requireCanSeeChannels(t, principal, channelRead, channelExecute, channelCreate, channelUpdate, channelDelete) + requireCannotSeeChannels(t, principal, channelWrite) } // Encode the given string to base64. @@ -1560,7 +1552,7 @@ func TestRevocationScenario1(t *testing.T) { // Ensure user can see ch1 (via role) // Verify history assert.ElementsMatch(t, []string{"!", "ch1"}, fooPrincipal.Channels().AllKeys()) - assert.True(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCanSeeChannels(t, aliceUserPrincipal, "ch1") assert.Len(t, aliceUserPrincipal.RoleHistory(), 0) assert.Len(t, aliceUserPrincipal.ChannelHistory(), 0) assert.Len(t, fooPrincipal.ChannelHistory(), 0) @@ -1573,7 +1565,7 @@ func TestRevocationScenario1(t *testing.T) { // Ensure user can see ch1 (via role) // Verify history assert.ElementsMatch(t, []string{"!", "ch1"}, fooPrincipal.Channels().AllKeys()) - assert.True(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCanSeeChannels(t, aliceUserPrincipal, "ch1") assert.Len(t, aliceUserPrincipal.RoleHistory(), 0) assert.Len(t, aliceUserPrincipal.ChannelHistory(), 0) assert.Len(t, fooPrincipal.ChannelHistory(), 0) @@ -1592,7 +1584,7 @@ func TestRevocationScenario1(t *testing.T) { // Ensure user can see ch1 (via role) // Verify history assert.ElementsMatch(t, []string{"!", "ch1"}, fooPrincipal.Channels().AllKeys()) - assert.True(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCanSeeChannels(t, aliceUserPrincipal, "ch1") assert.Len(t, aliceUserPrincipal.RoleHistory(), 0) assert.Len(t, aliceUserPrincipal.ChannelHistory(), 0) assert.Len(t, fooPrincipal.ChannelHistory(), 0) @@ -1607,7 +1599,7 @@ func TestRevocationScenario1(t *testing.T) { // Ensure user cannot see ch1 (via role) // Verify history - assert.False(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCannotSeeChannels(t, aliceUserPrincipal, "ch1") userRoleHistory, ok := aliceUserPrincipal.RoleHistory()["foo"] require.True(t, ok) assert.Equal(t, GrantHistorySequencePair{StartSeq: 65, EndSeq: 95}, userRoleHistory.Entries[0]) @@ -1655,7 +1647,7 @@ func TestRevocationScenario2(t *testing.T) { // Ensure user can see ch1 (via role) // Verify history assert.ElementsMatch(t, []string{"!", "ch1"}, fooPrincipal.Channels().AllKeys()) - assert.True(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCanSeeChannels(t, aliceUserPrincipal, "ch1") assert.Len(t, aliceUserPrincipal.RoleHistory(), 0) assert.Len(t, aliceUserPrincipal.ChannelHistory(), 0) assert.Len(t, fooPrincipal.ChannelHistory(), 0) @@ -1669,7 +1661,7 @@ func TestRevocationScenario2(t *testing.T) { // Ensure user cannot see ch1 (via role) // Verify history - assert.False(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCannotSeeChannels(t, aliceUserPrincipal, "ch1") userRoleHistory, ok := aliceUserPrincipal.RoleHistory()["foo"] require.True(t, ok) assert.Equal(t, GrantHistorySequencePair{StartSeq: 20, EndSeq: 45}, userRoleHistory.Entries[0]) @@ -1688,7 +1680,7 @@ func TestRevocationScenario2(t *testing.T) { // Ensure user can see ch1 (via role) // Verify history - assert.True(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCanSeeChannels(t, aliceUserPrincipal, "ch1") assert.Equal(t, GrantHistorySequencePair{StartSeq: 20, EndSeq: 45}, userRoleHistory.Entries[0]) assert.Len(t, aliceUserPrincipal.ChannelHistory(), 0) assert.Len(t, fooPrincipal.ChannelHistory(), 0) @@ -1703,7 +1695,7 @@ func TestRevocationScenario2(t *testing.T) { // Ensure user cannot see ch1 (via role) // Verify history - assert.False(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCannotSeeChannels(t, aliceUserPrincipal, "ch1") userRoleHistory, ok = aliceUserPrincipal.RoleHistory()["foo"] require.True(t, ok) assert.Equal(t, GrantHistorySequencePair{StartSeq: 20, EndSeq: 45}, userRoleHistory.Entries[0]) @@ -1756,7 +1748,7 @@ func TestRevocationScenario3(t *testing.T) { // Ensure user can see ch1 (via role) // Verify history assert.ElementsMatch(t, []string{"!", "ch1"}, fooPrincipal.Channels().AllKeys()) - assert.True(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCanSeeChannels(t, aliceUserPrincipal, "ch1") assert.Len(t, aliceUserPrincipal.RoleHistory(), 0) assert.Len(t, aliceUserPrincipal.ChannelHistory(), 0) assert.Len(t, fooPrincipal.ChannelHistory(), 0) @@ -1771,7 +1763,7 @@ func TestRevocationScenario3(t *testing.T) { // Ensure user cannot see ch1 (via role) // Verify history - assert.False(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCannotSeeChannels(t, aliceUserPrincipal, "ch1") userRoleHistory, ok := aliceUserPrincipal.RoleHistory()["foo"] require.True(t, ok) assert.Equal(t, GrantHistorySequencePair{StartSeq: 20, EndSeq: 45}, userRoleHistory.Entries[0]) @@ -1794,7 +1786,7 @@ func TestRevocationScenario3(t *testing.T) { // Ensure user can see ch1 (via role) // Verify history - assert.True(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCanSeeChannels(t, aliceUserPrincipal, "ch1") assert.Len(t, aliceUserPrincipal.RoleHistory(), 1) assert.Len(t, fooPrincipal.ChannelHistory(), 1) assert.Len(t, aliceUserPrincipal.ChannelHistory(), 0) @@ -1810,7 +1802,7 @@ func TestRevocationScenario3(t *testing.T) { // Ensure user cannot see ch1 (via role) // Verify history - assert.False(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCannotSeeChannels(t, aliceUserPrincipal, "ch1") userRoleHistory, ok = aliceUserPrincipal.RoleHistory()["foo"] require.True(t, ok) @@ -1866,7 +1858,7 @@ func TestRevocationScenario4(t *testing.T) { // Ensure user can see ch1 (via role) // Verify history assert.ElementsMatch(t, []string{"!", "ch1"}, fooPrincipal.Channels().AllKeys()) - assert.True(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCanSeeChannels(t, aliceUserPrincipal, "ch1") assert.Len(t, aliceUserPrincipal.RoleHistory(), 0) assert.Len(t, aliceUserPrincipal.ChannelHistory(), 0) assert.Len(t, fooPrincipal.ChannelHistory(), 0) @@ -1883,7 +1875,7 @@ func TestRevocationScenario4(t *testing.T) { // Ensure user cannot see ch1 (via role) // Verify history - assert.False(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCannotSeeChannels(t, aliceUserPrincipal, "ch1") channelHistory, ok := fooPrincipal.ChannelHistory()["ch1"] require.True(t, ok) assert.Equal(t, GrantHistorySequencePair{StartSeq: 5, EndSeq: 55}, channelHistory.Entries[0]) @@ -1900,7 +1892,7 @@ func TestRevocationScenario4(t *testing.T) { // Ensure user can see ch1 (via role) // Verify history - assert.True(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCanSeeChannels(t, aliceUserPrincipal, "ch1") assert.Len(t, fooPrincipal.ChannelHistory(), 1) assert.Len(t, aliceUserPrincipal.RoleHistory(), 0) assert.Len(t, aliceUserPrincipal.ChannelHistory(), 0) @@ -1915,7 +1907,7 @@ func TestRevocationScenario4(t *testing.T) { // Ensure user cannot see ch1 (via role) // Verify history - assert.False(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCannotSeeChannels(t, aliceUserPrincipal, "ch1") userRoleHistory, ok := aliceUserPrincipal.RoleHistory()["foo"] require.True(t, ok) assert.Equal(t, GrantHistorySequencePair{StartSeq: 65, EndSeq: 95}, userRoleHistory.Entries[0]) @@ -1963,7 +1955,7 @@ func TestRevocationScenario5(t *testing.T) { // Ensure user can see ch1 (via role) // Verify history assert.ElementsMatch(t, []string{"!", "ch1"}, fooPrincipal.Channels().AllKeys()) - assert.True(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCanSeeChannels(t, aliceUserPrincipal, "ch1") assert.Len(t, aliceUserPrincipal.RoleHistory(), 0) assert.Len(t, aliceUserPrincipal.ChannelHistory(), 0) assert.Len(t, fooPrincipal.ChannelHistory(), 0) @@ -1981,7 +1973,7 @@ func TestRevocationScenario5(t *testing.T) { // Ensure user can see ch1 (via role) // Verify history - assert.True(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCanSeeChannels(t, aliceUserPrincipal, "ch1") assert.Len(t, aliceUserPrincipal.RoleHistory(), 0) assert.Len(t, aliceUserPrincipal.ChannelHistory(), 0) assert.Len(t, fooPrincipal.ChannelHistory(), 0) @@ -1996,7 +1988,7 @@ func TestRevocationScenario5(t *testing.T) { // Ensure user cannot see ch1 (via role) // Verify history - assert.False(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCannotSeeChannels(t, aliceUserPrincipal, "ch1") userRoleHistory, ok := aliceUserPrincipal.RoleHistory()["foo"] require.True(t, ok) assert.Equal(t, GrantHistorySequencePair{StartSeq: 65, EndSeq: 95}, userRoleHistory.Entries[0]) @@ -2044,7 +2036,7 @@ func TestRevocationScenario6(t *testing.T) { // Ensure user can see ch1 (via role) // Verify history assert.ElementsMatch(t, []string{"!", "ch1"}, fooPrincipal.Channels().AllKeys()) - assert.True(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCanSeeChannels(t, aliceUserPrincipal, "ch1") assert.Len(t, aliceUserPrincipal.RoleHistory(), 0) assert.Len(t, aliceUserPrincipal.ChannelHistory(), 0) assert.Len(t, fooPrincipal.ChannelHistory(), 0) @@ -2064,7 +2056,7 @@ func TestRevocationScenario6(t *testing.T) { // Ensure user cannot see ch1 (via role) // Verify history - assert.False(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCannotSeeChannels(t, aliceUserPrincipal, "ch1") channelHistory, ok := fooPrincipal.ChannelHistory()["ch1"] require.True(t, ok) assert.Equal(t, GrantHistorySequencePair{StartSeq: 5, EndSeq: 55}, channelHistory.Entries[0]) @@ -2081,7 +2073,7 @@ func TestRevocationScenario6(t *testing.T) { // Ensure user cannot see ch1 (via role) // Verify history - assert.False(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCannotSeeChannels(t, aliceUserPrincipal, "ch1") userRoleHistory, ok := aliceUserPrincipal.RoleHistory()["foo"] require.True(t, ok) assert.Equal(t, GrantHistorySequencePair{StartSeq: 65, EndSeq: 95}, userRoleHistory.Entries[0]) @@ -2129,7 +2121,7 @@ func TestRevocationScenario7(t *testing.T) { // Ensure user can see ch1 (via role) // Verify history assert.ElementsMatch(t, []string{"!", "ch1"}, fooPrincipal.Channels().AllKeys()) - assert.True(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCanSeeChannels(t, aliceUserPrincipal, "ch1") assert.Len(t, aliceUserPrincipal.RoleHistory(), 0) assert.Len(t, aliceUserPrincipal.ChannelHistory(), 0) assert.Len(t, fooPrincipal.ChannelHistory(), 0) @@ -2150,7 +2142,7 @@ func TestRevocationScenario7(t *testing.T) { // Ensure user cannot see ch1 (via role) // Verify history - assert.False(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCannotSeeChannels(t, aliceUserPrincipal, "ch1") userRoleHistory, ok := aliceUserPrincipal.RoleHistory()["foo"] require.True(t, ok) assert.Equal(t, GrantHistorySequencePair{StartSeq: 20, EndSeq: 45}, userRoleHistory.Entries[0]) @@ -2170,7 +2162,7 @@ func TestRevocationScenario7(t *testing.T) { // Ensure user cannot see ch1 (via role) // Verify history - assert.False(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCannotSeeChannels(t, aliceUserPrincipal, "ch1") assert.Len(t, aliceUserPrincipal.RoleHistory(), 1) assert.Len(t, fooPrincipal.ChannelHistory(), 1) @@ -2212,7 +2204,7 @@ func TestRevocationScenario8(t *testing.T) { // Ensure user cannot see ch1 (via role) // Verify history - assert.False(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCannotSeeChannels(t, aliceUserPrincipal, "ch1") assert.Len(t, aliceUserPrincipal.RoleHistory(), 0) assert.Len(t, aliceUserPrincipal.ChannelHistory(), 0) assert.Len(t, fooPrincipal.ChannelHistory(), 0) @@ -2232,7 +2224,7 @@ func TestRevocationScenario8(t *testing.T) { // Ensure user cannot see ch1 (via role) // Verify history - assert.False(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCannotSeeChannels(t, aliceUserPrincipal, "ch1") channelHistory, ok := fooPrincipal.ChannelHistory()["ch1"] require.True(t, ok) assert.Equal(t, GrantHistorySequencePair{StartSeq: 5, EndSeq: 55}, channelHistory.Entries[0]) @@ -2276,7 +2268,7 @@ func TestRevocationScenario9(t *testing.T) { // Ensure user cannot see ch1 (via role) // Verify history - assert.False(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCannotSeeChannels(t, aliceUserPrincipal, "ch1") assert.Len(t, aliceUserPrincipal.RoleHistory(), 0) assert.Len(t, aliceUserPrincipal.ChannelHistory(), 0) assert.Len(t, fooPrincipal.ChannelHistory(), 0) @@ -2294,7 +2286,7 @@ func TestRevocationScenario9(t *testing.T) { // Ensure user cannot see ch1 (via role) // Verify history - assert.False(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCannotSeeChannels(t, aliceUserPrincipal, "ch1") assert.Len(t, aliceUserPrincipal.RoleHistory(), 0) assert.Len(t, aliceUserPrincipal.ChannelHistory(), 0) assert.Len(t, fooPrincipal.ChannelHistory(), 0) @@ -2338,7 +2330,7 @@ func TestRevocationScenario10(t *testing.T) { // Ensure user cannot see ch1 (via role) // Verify history - assert.False(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCannotSeeChannels(t, aliceUserPrincipal, "ch1") assert.Len(t, aliceUserPrincipal.RoleHistory(), 0) assert.Len(t, aliceUserPrincipal.ChannelHistory(), 0) assert.Len(t, fooPrincipal.ChannelHistory(), 0) @@ -2355,7 +2347,7 @@ func TestRevocationScenario10(t *testing.T) { // Ensure user cannot see ch1 (via role) // Verify history - assert.False(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCannotSeeChannels(t, aliceUserPrincipal, "ch1") userRoleHistory, ok := aliceUserPrincipal.RoleHistory()["foo"] require.True(t, ok) assert.Equal(t, GrantHistorySequencePair{StartSeq: 65, EndSeq: 95}, userRoleHistory.Entries[0]) @@ -2402,7 +2394,7 @@ func TestRevocationScenario11(t *testing.T) { // Ensure user can see ch1 (via role) // Verify history - assert.True(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCanSeeChannels(t, aliceUserPrincipal, "ch1") assert.Len(t, aliceUserPrincipal.RoleHistory(), 0) assert.Len(t, aliceUserPrincipal.ChannelHistory(), 0) assert.Len(t, fooPrincipal.ChannelHistory(), 0) @@ -2417,7 +2409,7 @@ func TestRevocationScenario11(t *testing.T) { // Ensure user cannot see ch1 (via role) // Verify history - assert.False(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCannotSeeChannels(t, aliceUserPrincipal, "ch1") userRoleHistory, ok := aliceUserPrincipal.RoleHistory()["foo"] require.True(t, ok) @@ -2473,7 +2465,7 @@ func TestRevocationScenario12(t *testing.T) { // Ensure user cannot see ch1 (via role) // Verify history - assert.False(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCannotSeeChannels(t, aliceUserPrincipal, "ch1") assert.Len(t, aliceUserPrincipal.RoleHistory(), 0) assert.Len(t, aliceUserPrincipal.ChannelHistory(), 0) assert.Len(t, fooPrincipal.ChannelHistory(), 0) @@ -2487,7 +2479,7 @@ func TestRevocationScenario12(t *testing.T) { // Ensure user cannot see ch1 (via role) // Verify history - assert.False(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCannotSeeChannels(t, aliceUserPrincipal, "ch1") userRoleHistory, ok := aliceUserPrincipal.RoleHistory()["foo"] require.True(t, ok) assert.Equal(t, GrantHistorySequencePair{StartSeq: 65, EndSeq: 95}, userRoleHistory.Entries[0]) @@ -2537,7 +2529,7 @@ func TestRevocationScenario13(t *testing.T) { // Ensure user cannot see ch1 (via role) // Verify history - assert.False(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCannotSeeChannels(t, aliceUserPrincipal, "ch1") assert.Len(t, aliceUserPrincipal.RoleHistory(), 0) assert.Len(t, aliceUserPrincipal.ChannelHistory(), 0) assert.Len(t, fooPrincipal.ChannelHistory(), 0) @@ -2549,7 +2541,7 @@ func TestRevocationScenario13(t *testing.T) { // Ensure user cannot see ch1 (via role) // Verify history - assert.False(t, aliceUserPrincipal.canSeeChannel("ch1")) + requireCannotSeeChannels(t, aliceUserPrincipal, "ch1") assert.Len(t, aliceUserPrincipal.RoleHistory(), 0) assert.Len(t, aliceUserPrincipal.ChannelHistory(), 0) assert.Len(t, fooPrincipal.ChannelHistory(), 0) @@ -2601,7 +2593,7 @@ func TestRevocationScenario14(t *testing.T) { assert.Equal(t, uint64(45), revokedChannelsCombined["ch1"]) // Ensure that the user cannot see the channel at this point (after 45) - aliceUserPrincipal.canSeeChannel("ch1") + requireCannotSeeChannels(t, aliceUserPrincipal, "ch1") // Ensure a pull from 45 (same seq as revocation) wouldn't send message revokedChannelsCombined = aliceUserPrincipal.revokedChannels(45, 0, 0) @@ -2967,3 +2959,8 @@ func TestCalculateMaxHistoryEntriesPerGrant(t *testing.T) { }) } } + +// requireExpandWildCardChannel is a helper function to assert that a user's wildcard channel expansion produces the expected result. +func requireExpandWildCardChannel(t *testing.T, user User, expectedChannels, channelsToExpand []string) { + assert.Equal(t, base.SetFromArray(expectedChannels), user.expandWildCardChannel(base.SetFromArray(channelsToExpand)), "Expected channels %v to expand to %v", expectedChannels, channelsToExpand) +} diff --git a/auth/collection_access_test.go b/auth/collection_access_test.go index 3958b83746..a46a216a1f 100644 --- a/auth/collection_access_test.go +++ b/auth/collection_access_test.go @@ -19,13 +19,18 @@ import ( "github.com/stretchr/testify/require" ) -func canSeeAllCollectionChannels(scope, collection string, princ Principal, channels base.Set) bool { - for channel := range channels { - if !princ.CanSeeCollectionChannel(scope, collection, channel) { - return false - } +// requireCanSeeCollectionChannels asserts that the principal can see all the specified channels in the given collection +func requireCanSeeCollectionChannels(t *testing.T, scope, collection string, princ Principal, channels ...string) { + for _, channel := range channels { + require.True(t, princ.CanSeeCollectionChannel(scope, collection, channel), "Expected %s to be able to see channel %q in %s.%s", princ.Name(), channel, scope, collection) + } +} + +// requireCannotSeeCollectionChannels asserts that the principal cannot see any of the specified channels in the given collection +func requireCannotSeeCollectionChannels(t *testing.T, scope, collection string, princ Principal, channels ...string) { + for _, channel := range channels { + require.False(t, princ.CanSeeCollectionChannel(scope, collection, channel), "Expected %s to NOT be able to see channel %q in %s.%s", princ.Name(), channel, scope, collection) } - return true } func TestUserCollectionAccess(t *testing.T) { @@ -48,122 +53,114 @@ func TestUserCollectionAccess(t *testing.T) { otherCollection := "collection2" nonMatchingCollections := [][2]string{{base.DefaultScope, base.DefaultCollection}, {scope, otherCollection}, {otherScope, collection}, {otherScope, otherCollection}} // Default collection checks - should not have access based on authenticator - assert.Equal(t, ch.BaseSetOf(t), user.expandWildCardChannel(ch.BaseSetOf(t, "*"))) - assert.False(t, user.canSeeChannel("x")) - assert.False(t, user.canSeeChannel("!")) - assert.True(t, canSeeAllChannels(user, ch.BaseSetOf(t))) - assert.False(t, canSeeAllChannels(user, ch.BaseSetOf(t, "x"))) - assert.False(t, canSeeAllChannels(user, ch.BaseSetOf(t, "x", "y"))) - assert.False(t, canSeeAllChannels(user, ch.BaseSetOf(t, "*"))) - assert.False(t, user.authorizeAllChannels(ch.BaseSetOf(t, "*")) == nil) - assert.False(t, user.authorizeAnyChannel(ch.BaseSetOf(t, "x", "y")) == nil) - assert.False(t, user.authorizeAnyChannel(ch.BaseSetOf(t)) == nil) + requireExpandWildCardChannel(t, user, nil, []string{"*"}) + requireCannotSeeChannels(t, user, "x", "y", "!", "*") + require.ErrorIs(t, user.authorizeAllChannels(ch.BaseSetOf(t, "*")), errNotAllowedChannels) + require.ErrorIs(t, user.authorizeAnyChannel(ch.BaseSetOf(t, "x", "y")), errUnauthorized) + require.ErrorIs(t, user.authorizeAnyChannel(ch.BaseSetOf(t)), errUnauthorized) // Named collection checks - assert.Equal(t, ch.BaseSetOf(t, "!"), user.expandCollectionWildCardChannel(scope, collection, ch.BaseSetOf(t, "*"))) - assert.True(t, canSeeAllCollectionChannels(scope, collection, user, ch.BaseSetOf(t))) - assert.False(t, canSeeAllCollectionChannels(scope, collection, user, ch.BaseSetOf(t, "x"))) - assert.False(t, canSeeAllCollectionChannels(scope, collection, user, ch.BaseSetOf(t, "x", "y"))) - assert.False(t, canSeeAllCollectionChannels(scope, collection, user, ch.BaseSetOf(t, "*"))) - assert.False(t, user.authorizeAllCollectionChannels(scope, collection, ch.BaseSetOf(t, "*")) == nil) - assert.False(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t, "x", "y")) == nil) - assert.False(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t)) == nil) + requireExpandCollectionWildCardChannels(t, user, scope, collection, []string{"!"}, []string{"*"}) + requireCannotSeeCollectionChannels(t, scope, collection, user, "x", "y", "*") + require.ErrorIs(t, user.authorizeAllCollectionChannels(scope, collection, ch.BaseSetOf(t, "*")), errNotAllowedChannels) + require.ErrorIs(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t, "x", "y")), errUnauthorized) + require.ErrorIs(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t)), errUnauthorized) // User with access to one channel in named collection: user.setCollectionChannels(scope, collection, ch.AtSequence(ch.BaseSetOf(t, "x"), 1)) // Matching named collection checks - assert.Equal(t, ch.BaseSetOf(t, "x"), user.expandCollectionWildCardChannel(scope, collection, ch.BaseSetOf(t, "*"))) - assert.True(t, canSeeAllCollectionChannels(scope, collection, user, ch.BaseSetOf(t))) - assert.True(t, canSeeAllCollectionChannels(scope, collection, user, ch.BaseSetOf(t, "x"))) - assert.False(t, canSeeAllCollectionChannels(scope, collection, user, ch.BaseSetOf(t, "x", "y"))) - assert.False(t, user.authorizeAllCollectionChannels(scope, collection, ch.BaseSetOf(t, "x", "y")) == nil) - assert.False(t, user.authorizeAllCollectionChannels(scope, collection, ch.BaseSetOf(t, "*")) == nil) - assert.True(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t, "x", "y")) == nil) - assert.False(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t, "y")) == nil) - assert.False(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t)) == nil) + requireExpandCollectionWildCardChannels(t, user, scope, collection, []string{"x"}, []string{"*"}) + requireCanSeeCollectionChannels(t, scope, collection, user, "x") + requireCannotSeeCollectionChannels(t, scope, collection, user, "y", "!", "*") + require.ErrorIs(t, user.authorizeAllCollectionChannels(scope, collection, ch.BaseSetOf(t, "x", "y")), errNotAllowedChannels) + require.ErrorIs(t, user.authorizeAllCollectionChannels(scope, collection, ch.BaseSetOf(t, "*")), errNotAllowedChannels) + require.NoError(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t, "x", "y"))) + require.ErrorIs(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t, "y")), errUnauthorized) + require.ErrorIs(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t)), errUnauthorized) // Non-matching collection checks for _, pair := range nonMatchingCollections { s := pair[0] c := pair[1] - assert.Equal(t, ch.BaseSetOf(t), user.expandCollectionWildCardChannel(s, c, ch.BaseSetOf(t, "*"))) - assert.True(t, canSeeAllCollectionChannels(s, c, user, ch.BaseSetOf(t))) - assert.False(t, canSeeAllCollectionChannels(s, c, user, ch.BaseSetOf(t, "x"))) - assert.False(t, canSeeAllCollectionChannels(s, c, user, ch.BaseSetOf(t, "x", "y"))) - assert.False(t, user.authorizeAllCollectionChannels(s, c, ch.BaseSetOf(t, "x", "y")) == nil) - assert.False(t, user.authorizeAllCollectionChannels(s, c, ch.BaseSetOf(t, "*")) == nil) - assert.False(t, user.AuthorizeAnyCollectionChannel(s, c, ch.BaseSetOf(t, "x", "y")) == nil) - assert.False(t, user.AuthorizeAnyCollectionChannel(s, c, ch.BaseSetOf(t, "y")) == nil) - assert.False(t, user.AuthorizeAnyCollectionChannel(s, c, ch.BaseSetOf(t)) == nil) + requireExpandCollectionWildCardChannels(t, user, s, c, nil, []string{"*"}) + requireCannotSeeCollectionChannels(t, s, c, user, "x", "y", "!", "*") + if base.IsDefaultCollection(s, c) { + require.ErrorIs(t, user.authorizeAllCollectionChannels(s, c, ch.BaseSetOf(t, "x", "y")), errNotAllowedChannels, "for %s.%s", s, c) + require.ErrorIs(t, user.authorizeAllCollectionChannels(s, c, ch.BaseSetOf(t, "*")), errNotAllowedChannels, "for %s.%s", s, c) + } else { + require.ErrorIs(t, user.authorizeAllCollectionChannels(s, c, ch.BaseSetOf(t, "x", "y")), errUnauthorizedChannels, "for %s.%s", s, c) + require.ErrorIs(t, user.authorizeAllCollectionChannels(s, c, ch.BaseSetOf(t, "*")), errUnauthorizedChannels, "for %s.%s", s, c) + } + require.ErrorIs(t, user.AuthorizeAnyCollectionChannel(s, c, ch.BaseSetOf(t, "x", "y")), errUnauthorized) + require.ErrorIs(t, user.AuthorizeAnyCollectionChannel(s, c, ch.BaseSetOf(t, "y")), errUnauthorized) + require.ErrorIs(t, user.AuthorizeAnyCollectionChannel(s, c, ch.BaseSetOf(t)), errUnauthorized) } // User with access to two channels: // User with access to one channel in named collection: user.setCollectionChannels(scope, collection, ch.AtSequence(ch.BaseSetOf(t, "x", "y"), 1)) // Matching named collection checks - assert.Equal(t, ch.BaseSetOf(t, "x", "y"), user.expandCollectionWildCardChannel(scope, collection, ch.BaseSetOf(t, "*"))) - assert.True(t, canSeeAllCollectionChannels(scope, collection, user, ch.BaseSetOf(t))) - assert.True(t, canSeeAllCollectionChannels(scope, collection, user, ch.BaseSetOf(t, "x"))) - assert.True(t, canSeeAllCollectionChannels(scope, collection, user, ch.BaseSetOf(t, "x", "y"))) - assert.False(t, canSeeAllCollectionChannels(scope, collection, user, ch.BaseSetOf(t, "x", "y", "z"))) - assert.True(t, user.authorizeAllCollectionChannels(scope, collection, ch.BaseSetOf(t, "x", "y")) == nil) - assert.False(t, user.authorizeAllCollectionChannels(scope, collection, ch.BaseSetOf(t, "*")) == nil) - assert.True(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t, "x", "y")) == nil) - assert.True(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t, "y")) == nil) - assert.False(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t, "z")) == nil) - assert.False(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t)) == nil) + requireExpandCollectionWildCardChannels(t, user, scope, collection, []string{"x", "y"}, []string{"*"}) + requireCanSeeCollectionChannels(t, scope, collection, user, "x", "y") + requireCannotSeeCollectionChannels(t, scope, collection, user, "z") + require.NoError(t, user.authorizeAllCollectionChannels(scope, collection, ch.BaseSetOf(t, "x", "y"))) + require.ErrorIs(t, user.authorizeAllCollectionChannels(scope, collection, ch.BaseSetOf(t, "*")), errNotAllowedChannels) + require.NoError(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t, "x", "y"))) + require.NoError(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t, "y"))) + require.ErrorIs(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t, "z")), errUnauthorized) + require.ErrorIs(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t)), errUnauthorized) // Non-matching collection checks for _, pair := range nonMatchingCollections { s := pair[0] c := pair[1] - assert.Equal(t, ch.BaseSetOf(t), user.expandCollectionWildCardChannel(s, c, ch.BaseSetOf(t, "*"))) - assert.True(t, canSeeAllCollectionChannels(s, c, user, ch.BaseSetOf(t))) - assert.False(t, canSeeAllCollectionChannels(s, c, user, ch.BaseSetOf(t, "x"))) - assert.False(t, canSeeAllCollectionChannels(s, c, user, ch.BaseSetOf(t, "x", "y"))) - assert.False(t, user.authorizeAllCollectionChannels(s, c, ch.BaseSetOf(t, "x", "y")) == nil) - assert.False(t, user.authorizeAllCollectionChannels(s, c, ch.BaseSetOf(t, "*")) == nil) - assert.False(t, user.AuthorizeAnyCollectionChannel(s, c, ch.BaseSetOf(t, "x", "y")) == nil) - assert.False(t, user.AuthorizeAnyCollectionChannel(s, c, ch.BaseSetOf(t, "y")) == nil) - assert.False(t, user.AuthorizeAnyCollectionChannel(s, c, ch.BaseSetOf(t)) == nil) + requireExpandCollectionWildCardChannels(t, user, s, c, nil, []string{"*"}) + requireCannotSeeCollectionChannels(t, s, c, user, "x", "y", "z", "!", "*") + if base.IsDefaultCollection(s, c) { + require.ErrorIs(t, user.authorizeAllCollectionChannels(s, c, ch.BaseSetOf(t, "x", "y")), errNotAllowedChannels, "for %s.%s", s, c) + require.ErrorIs(t, user.authorizeAllCollectionChannels(s, c, ch.BaseSetOf(t, "*")), errNotAllowedChannels, "for %s.%s", s, c) + } else { + require.ErrorIs(t, user.authorizeAllCollectionChannels(s, c, ch.BaseSetOf(t, "x", "y")), errUnauthorizedChannels, "for %s.%s", s, c) + require.ErrorIs(t, user.authorizeAllCollectionChannels(s, c, ch.BaseSetOf(t, "*")), errUnauthorizedChannels, "for %s.%s", s, c) + } + require.ErrorIs(t, user.AuthorizeAnyCollectionChannel(s, c, ch.BaseSetOf(t, "x", "y")), errUnauthorized, "for %s.%s", s, c) + require.ErrorIs(t, user.AuthorizeAnyCollectionChannel(s, c, ch.BaseSetOf(t, "y")), errUnauthorized, "for %s.%s", s, c) + require.ErrorIs(t, user.AuthorizeAnyCollectionChannel(s, c, ch.BaseSetOf(t)), errUnauthorized, "for %s.%s", s, c) } // User with wildcard access: user.setCollectionChannels(scope, collection, ch.AtSequence(ch.BaseSetOf(t, "*", "q"), 1)) // Legacy default collection checks - assert.Equal(t, ch.BaseSetOf(t), user.expandWildCardChannel(ch.BaseSetOf(t, "*"))) - assert.False(t, user.canSeeChannel("*")) - assert.True(t, canSeeAllChannels(user, ch.BaseSetOf(t))) - assert.False(t, canSeeAllChannels(user, ch.BaseSetOf(t, "x"))) - assert.False(t, canSeeAllChannels(user, ch.BaseSetOf(t, "x", "y"))) - assert.False(t, user.authorizeAllChannels(ch.BaseSetOf(t, "x", "y")) == nil) - assert.False(t, user.authorizeAllChannels(ch.BaseSetOf(t, "*")) == nil) - assert.False(t, user.authorizeAnyChannel(ch.BaseSetOf(t, "x")) == nil) - assert.False(t, user.authorizeAnyChannel(ch.BaseSetOf(t, "*")) == nil) - assert.False(t, user.authorizeAnyChannel(ch.BaseSetOf(t)) == nil) + requireExpandWildCardChannel(t, user, nil, []string{"*"}) + requireCannotSeeChannels(t, user, "x", "y", "q", "!", "*") + require.ErrorIs(t, user.authorizeAllChannels(ch.BaseSetOf(t, "x", "y")), errNotAllowedChannels) + require.ErrorIs(t, user.authorizeAllChannels(ch.BaseSetOf(t, "*")), errNotAllowedChannels) + require.ErrorIs(t, user.authorizeAnyChannel(ch.BaseSetOf(t, "x")), errUnauthorized) + require.ErrorIs(t, user.authorizeAnyChannel(ch.BaseSetOf(t, "*")), errUnauthorized) + require.ErrorIs(t, user.authorizeAnyChannel(ch.BaseSetOf(t)), errUnauthorized) // Matching named collection checks - assert.Equal(t, ch.BaseSetOf(t, "*", "q"), user.expandCollectionWildCardChannel(scope, collection, ch.BaseSetOf(t, "*"))) - assert.True(t, canSeeAllCollectionChannels(scope, collection, user, ch.BaseSetOf(t))) - assert.True(t, canSeeAllCollectionChannels(scope, collection, user, ch.BaseSetOf(t, "x"))) - assert.True(t, canSeeAllCollectionChannels(scope, collection, user, ch.BaseSetOf(t, "x", "y"))) - assert.True(t, canSeeAllCollectionChannels(scope, collection, user, ch.BaseSetOf(t, "x", "y", "z"))) - assert.True(t, user.authorizeAllCollectionChannels(scope, collection, ch.BaseSetOf(t, "x", "y")) == nil) - assert.True(t, user.authorizeAllCollectionChannels(scope, collection, ch.BaseSetOf(t, "*")) == nil) - assert.True(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t, "x", "y")) == nil) - assert.True(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t, "y")) == nil) - assert.True(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t, "z")) == nil) - assert.True(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t)) == nil) + requireExpandCollectionWildCardChannels(t, user, scope, collection, []string{"*", "q"}, []string{"*"}) + requireCanSeeCollectionChannels(t, scope, collection, user, "x", "y", "z", "q", "*") + require.NoError(t, user.authorizeAllCollectionChannels(scope, collection, ch.BaseSetOf(t, "x", "y"))) + require.NoError(t, user.authorizeAllCollectionChannels(scope, collection, ch.BaseSetOf(t, "*"))) + require.NoError(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t, "x", "y"))) + require.NoError(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t, "y"))) + require.NoError(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t, "z"))) + require.NoError(t, user.AuthorizeAnyCollectionChannel(scope, collection, ch.BaseSetOf(t))) // Non-matching collection checks for _, pair := range nonMatchingCollections { s := pair[0] c := pair[1] - assert.Equal(t, ch.BaseSetOf(t), user.expandCollectionWildCardChannel(s, c, ch.BaseSetOf(t, "*"))) - assert.True(t, canSeeAllCollectionChannels(s, c, user, ch.BaseSetOf(t))) - assert.False(t, canSeeAllCollectionChannels(s, c, user, ch.BaseSetOf(t, "x"))) - assert.False(t, canSeeAllCollectionChannels(s, c, user, ch.BaseSetOf(t, "x", "y"))) - assert.False(t, user.authorizeAllCollectionChannels(s, c, ch.BaseSetOf(t, "x", "y")) == nil) - assert.False(t, user.authorizeAllCollectionChannels(s, c, ch.BaseSetOf(t, "*")) == nil) - assert.False(t, user.AuthorizeAnyCollectionChannel(s, c, ch.BaseSetOf(t, "x", "y")) == nil) - assert.False(t, user.AuthorizeAnyCollectionChannel(s, c, ch.BaseSetOf(t, "y")) == nil) - assert.False(t, user.AuthorizeAnyCollectionChannel(s, c, ch.BaseSetOf(t)) == nil) + requireExpandCollectionWildCardChannels(t, user, s, c, nil, []string{"*"}) + requireCannotSeeCollectionChannels(t, s, c, user, "x", "y", "z", "q", "!", "*") + if base.IsDefaultCollection(s, c) { + require.ErrorIs(t, user.authorizeAllCollectionChannels(s, c, ch.BaseSetOf(t, "x", "y")), errNotAllowedChannels) + require.ErrorIs(t, user.authorizeAllCollectionChannels(s, c, ch.BaseSetOf(t, "*")), errNotAllowedChannels) + } else { + require.ErrorIs(t, user.authorizeAllCollectionChannels(s, c, ch.BaseSetOf(t, "x", "y")), errUnauthorizedChannels) + require.ErrorIs(t, user.authorizeAllCollectionChannels(s, c, ch.BaseSetOf(t, "*")), errUnauthorizedChannels) + } + require.ErrorIs(t, user.AuthorizeAnyCollectionChannel(s, c, ch.BaseSetOf(t, "x", "y")), errUnauthorized) + require.ErrorIs(t, user.AuthorizeAnyCollectionChannel(s, c, ch.BaseSetOf(t, "y")), errUnauthorized) + require.ErrorIs(t, user.AuthorizeAnyCollectionChannel(s, c, ch.BaseSetOf(t)), errUnauthorized) } } @@ -194,7 +191,7 @@ func TestSerializeUserWithCollections(t *testing.T) { require.True(t, ok) ch, exists := collectionAccess.ExplicitChannels_["x"] require.True(t, exists) - assert.True(t, ch.Sequence == 1) + require.Equal(t, uint64(1), ch.Sequence) // Remove all channels for scope and collection user.SetCollectionExplicitChannels(scope, collection, nil, 2) @@ -277,3 +274,8 @@ func TestPrincipalConfigSetExplicitChannels(t *testing.T) { }) } + +// requireExpandCollectionWildCardChannels asserts that the channels will be expanded to the expected channels for the given collection +func requireExpandCollectionWildCardChannels(t *testing.T, user User, scope, collection string, expectedChannels []string, channelsToExpand []string) { + require.Equal(t, base.SetFromArray(expectedChannels), user.expandCollectionWildCardChannel(scope, collection, base.SetFromArray(channelsToExpand)), "Expected channels %v for %s.%s from %v on user %s", expectedChannels, scope, collection, channelsToExpand, user.Name()) +} diff --git a/auth/error.go b/auth/error.go new file mode 100644 index 0000000000..a51b34a783 --- /dev/null +++ b/auth/error.go @@ -0,0 +1,36 @@ +// Copyright 2025-Present Couchbase, Inc. +// +// Use of this software is governed by the Business Source License included +// in the file licenses/BSL-Couchbase.txt. As of the Change Date specified +// in that file, in accordance with the Business Source License, use of this +// software will be governed by the Apache License, Version 2.0, included in +// the file licenses/APL2.txt. + +package auth + +import ( + "fmt" + "net/http" + + "github.com/couchbase/sync_gateway/base" +) + +var ( + errLoginRequired = base.HTTPErrorf(http.StatusUnauthorized, "login required") + // errUnauthorized is a generic error message + errUnauthorized = base.HTTPErrorf(http.StatusForbidden, "You are not allowed to see this") + // errUnauthorizedChannels is used when we cannot determine which channels are unauthorized + errUnauthorizedChannels = base.HTTPErrorf(http.StatusForbidden, "Unauthorized to see channels") + // errNotAllowedChannels is used when we can determine which channels are not allowed + errNotAllowedChannels = base.HTTPErrorf(http.StatusForbidden, "You are not allowed to see channels") +) + +// newErrUnauthorizedChannels creates an error indicating the user is not authorized to see the specified channels. Used when we can not determine which channels are unauthorized. +func newErrUnauthorizedChannels(channels base.Set) error { + return fmt.Errorf("%w %v", errUnauthorizedChannels, channels) +} + +// newErrNotAllowedChannels creates an error indicating the user is not allowed to see the specified channels. Used when we can determine which channels are not allowed. +func newErrNotAllowedChannels[T base.Set | []string](channels T) error { + return fmt.Errorf("%w %v", errNotAllowedChannels, channels) +} diff --git a/auth/principal.go b/auth/principal.go index d1b3e46b7c..64f69bd895 100644 --- a/auth/principal.go +++ b/auth/principal.go @@ -39,7 +39,7 @@ type Principal interface { // Returns an appropriate HTTPError for unauthorized access -- a 401 if the receiver is // the guest user, else 403. - UnauthError(message string) error + UnauthError(err error) error DocID() string accessViewKey() string diff --git a/auth/role.go b/auth/role.go index 3cc23d07a8..47b5735f2d 100644 --- a/auth/role.go +++ b/auth/role.go @@ -363,11 +363,12 @@ func (role *roleImpl) validate() error { //////// CHANNEL AUTHORIZATION: -func (role *roleImpl) UnauthError(message string) error { +// UnauthError returns the underlying error unless Role is the guest user. +func (role *roleImpl) UnauthError(err error) error { if role.Name_ == "" { - return base.HTTPErrorf(http.StatusUnauthorized, "login required: %s", message) + return errLoginRequired } - return base.NewHTTPError(http.StatusForbidden, message) + return err } // Returns true if the Role is allowed to access the channel. @@ -406,7 +407,7 @@ func authorizeAllChannels(princ Principal, channels base.Set) error { } } if forbidden != nil { - return princ.UnauthError(fmt.Sprintf("You are not allowed to see channels %v", forbidden)) + return princ.UnauthError(newErrNotAllowedChannels(forbidden)) } return nil } @@ -423,5 +424,5 @@ func authorizeAnyChannel(princ Principal, channels base.Set) error { } else if princ.Channels().Contains(ch.UserStarChannel) { return nil } - return princ.UnauthError("You are not allowed to see this") + return princ.UnauthError(errUnauthorized) } diff --git a/auth/role_collection_access.go b/auth/role_collection_access.go index 8916b808bf..e2be51529a 100644 --- a/auth/role_collection_access.go +++ b/auth/role_collection_access.go @@ -9,8 +9,6 @@ package auth import ( - "fmt" - "github.com/couchbase/sync_gateway/base" ch "github.com/couchbase/sync_gateway/channels" ) @@ -195,11 +193,11 @@ func (role *roleImpl) authorizeAllCollectionChannels(scope, collection string, c } } if forbidden != nil { - return role.UnauthError(fmt.Sprintf("You are not allowed to see channels %v", forbidden)) + return role.UnauthError(newErrNotAllowedChannels(forbidden)) } return nil } - return role.UnauthError(fmt.Sprintf("Unauthorized to see channels %v", channels)) + return role.UnauthError(newErrUnauthorizedChannels(channels)) } // Returns an error if the Principal does not have access to any of the channels in the set. @@ -219,7 +217,7 @@ func (role *roleImpl) AuthorizeAnyCollectionChannel(scope, collection string, ch return nil } } - return role.UnauthError("You are not allowed to see this") + return role.UnauthError(errUnauthorized) } // initChannels grants the specified channels to the role as an admin grant, and performs diff --git a/auth/user_collection_access.go b/auth/user_collection_access.go index f19dfbc8fa..494111e14d 100644 --- a/auth/user_collection_access.go +++ b/auth/user_collection_access.go @@ -98,7 +98,7 @@ func (user *userImpl) AuthorizeAnyCollectionChannel(scope, collection string, ch } } - return user.UnauthError("You are not allowed to see this") + return user.UnauthError(errUnauthorized) } func (user *userImpl) canSeeCollectionChannelSince(scope, collection, channel string) uint64 { diff --git a/db/functions.go b/db/functions.go index 9b18602e30..309457b8c0 100644 --- a/db/functions.go +++ b/db/functions.go @@ -13,7 +13,6 @@ package db import ( "context" "encoding/json" - "fmt" "net/http" "time" @@ -130,6 +129,6 @@ func missingError(user auth.User, what string, name string) error { if user == nil { return base.HTTPErrorf(http.StatusNotFound, "no such %s %q", what, name) } else { - return user.UnauthError(fmt.Sprintf("you are not allowed to call %s %q", what, name)) + return user.UnauthError(base.HTTPErrorf(http.StatusForbidden, "you are not allowed to call %s %q", what, name)) } } diff --git a/db/functions/function.go b/db/functions/function.go index 875b93f2ef..4767f5640f 100644 --- a/db/functions/function.go +++ b/db/functions/function.go @@ -288,7 +288,7 @@ func (fn *functionImpl) authorize(user auth.User, args map[string]any) error { } } } - return user.UnauthError(fmt.Sprintf("you are not allowed to call %s %q", fn.typeName, fn.name)) + return user.UnauthError(base.HTTPErrorf(http.StatusForbidden, "You are not allowed to call %s %q", fn.typeName, fn.name)) } // Expands patterns of the form `${param}` in `pattern`, looking up each such