diff --git a/internal/auth/db_test.go b/internal/auth/db_test.go index 08cec1940c..932dc2cfdc 100644 --- a/internal/auth/db_test.go +++ b/internal/auth/db_test.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 -package auth +package auth_test import ( "context" diff --git a/internal/auth/ldap/testing.go b/internal/auth/ldap/testing.go index c33bf5f7d8..417bda9c21 100644 --- a/internal/auth/ldap/testing.go +++ b/internal/auth/ldap/testing.go @@ -12,6 +12,7 @@ import ( "crypto/x509/pkix" "encoding/json" "encoding/pem" + "fmt" "math/big" "net" "net/url" @@ -19,8 +20,11 @@ import ( "testing" "time" + "github.com/hashicorp/boundary/internal/auth" "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/boundary/internal/kms" wrapping "github.com/hashicorp/go-kms-wrapping/v2" + "github.com/hashicorp/go-uuid" "github.com/stretchr/testify/require" ) @@ -175,6 +179,21 @@ func TestAccount(t testing.TB, conn *db.DB, am *AuthMethod, loginName string, op return a } +// TestAuthMethodWithAccountInManagedGroup creates an authMethod, and an account within that authmethod, an +// LDAP managed group, and add the newly created account as a member of the LDAP managed group. +func TestAuthMethodWithAccountInManagedGroup(t *testing.T, conn *db.DB, kmsCache *kms.Kms, scopeId string) (auth.AuthMethod, auth.Account, auth.ManagedGroup) { + t.Helper() + uuid, err := uuid.GenerateUUID() + require.NoError(t, err) + ctx := context.Background() + databaseWrapper, err := kmsCache.GetWrapper(context.Background(), scopeId, kms.KeyPurposeDatabase) + require.NoError(t, err) + am := TestAuthMethod(t, conn, databaseWrapper, scopeId, []string{fmt.Sprintf("ldap://%s", uuid)}) + managedGroup := TestManagedGroup(t, conn, am, []string{uuid}) + acct := TestAccount(t, conn, am, "testacct", WithMemberOfGroups(ctx, uuid)) + return am, acct, managedGroup +} + // TestManagedGroup creates a test ldap managed group. func TestManagedGroup(t testing.TB, conn *db.DB, am *AuthMethod, grpNames []string, opt ...Option) *ManagedGroup { t.Helper() diff --git a/internal/auth/oidc/testing.go b/internal/auth/oidc/testing.go index 5ec87d0f0f..3d0876532b 100644 --- a/internal/auth/oidc/testing.go +++ b/internal/auth/oidc/testing.go @@ -24,6 +24,7 @@ import ( "testing" "time" + "github.com/hashicorp/boundary/internal/auth" "github.com/hashicorp/boundary/internal/auth/oidc/request" "github.com/hashicorp/boundary/internal/authtoken" "github.com/hashicorp/boundary/internal/db" @@ -32,6 +33,7 @@ import ( "github.com/hashicorp/boundary/internal/kms" "github.com/hashicorp/cap/oidc" wrapping "github.com/hashicorp/go-kms-wrapping/v2" + "github.com/hashicorp/go-uuid" "github.com/stretchr/testify/require" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -192,6 +194,25 @@ func TestAccount(t testing.TB, conn *db.DB, am *AuthMethod, subject string, opt return a } +// TestAuthMethodWithAccountInManagedGroup creates an authMethod, and an account within that authmethod, an +// OIDC managed group, and add the newly created account as a member of the OIDC managed group. +func TestAuthMethodWithAccountInManagedGroup(t *testing.T, conn *db.DB, kmsCache *kms.Kms, scopeId string) (auth.AuthMethod, auth.Account, auth.ManagedGroup) { + t.Helper() + uuid, err := uuid.GenerateUUID() + require.NoError(t, err) + databaseWrapper, err := kmsCache.GetWrapper(context.Background(), scopeId, kms.KeyPurposeDatabase) + require.NoError(t, err) + testAuthMethod := TestAuthMethod(t, conn, databaseWrapper, scopeId, ActivePublicState, + "alice-rp", "fido", + WithIssuer(TestConvertToUrls(t, fmt.Sprintf("https://%s.com", uuid))[0]), + WithSigningAlgs(Alg(oidc.RS256)), + WithApiUrl(TestConvertToUrls(t, fmt.Sprintf("https://%s.com/callback", uuid))[0])) + account := TestAccount(t, conn, testAuthMethod, "testacct") + managedGroup := TestManagedGroup(t, conn, testAuthMethod, `"/token/sub" matches ".*"`) + TestManagedGroupMember(t, conn, managedGroup.PublicId, account.PublicId) + return testAuthMethod, account, managedGroup +} + // TestManagedGroup creates a test oidc managed group. func TestManagedGroup(t testing.TB, conn *db.DB, am *AuthMethod, filter string, opt ...Option) *ManagedGroup { t.Helper() diff --git a/internal/auth/password/testing.go b/internal/auth/password/testing.go index 0273c28e7a..275acfe630 100644 --- a/internal/auth/password/testing.go +++ b/internal/auth/password/testing.go @@ -8,7 +8,10 @@ import ( "fmt" "testing" + "github.com/hashicorp/boundary/globals" + "github.com/hashicorp/boundary/internal/auth" "github.com/hashicorp/boundary/internal/db" + "github.com/hashicorp/go-uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -71,6 +74,16 @@ func TestMultipleAccounts(t testing.TB, conn *db.DB, authMethodId string, count return auts } +// TestAuthMethodWithAccount creates an authMethod and an account within that authmethod +// returing both the AM and the account +func TestAuthMethodWithAccount(t *testing.T, conn *db.DB) (auth.AuthMethod, auth.Account) { + authMethod := TestAuthMethod(t, conn, globals.GlobalPrefix) + loginName, err := uuid.GenerateUUID() + require.NoError(t, err) + acct := TestAccount(t, conn, authMethod.GetPublicId(), loginName) + return authMethod, acct +} + // TestAccount creates a password account to the provided DB with the provided // auth method id and loginName. The auth method must have been created // previously. See password.NewAccount(...) for a list of supported options. diff --git a/internal/auth/testing.go b/internal/auth/testing.go index 42297f7415..86e80e5425 100644 --- a/internal/auth/testing.go +++ b/internal/auth/testing.go @@ -10,9 +10,15 @@ import ( "github.com/hashicorp/boundary/internal/db" "github.com/hashicorp/boundary/internal/db/timestamp" + "github.com/hashicorp/boundary/internal/kms" "github.com/stretchr/testify/require" ) +type ( + TestAuthMethodWithAccountFunc func(t *testing.T, conn *db.DB) (AuthMethod, Account) + TestAuthMethodWithAccountInManagedGroup func(t *testing.T, conn *db.DB, kmsCache *kms.Kms, scopeId string) (AuthMethod, Account, ManagedGroup) +) + // ManagedGroupMemberAccount represents an entry from // auth_managed_group_member_account. These are used to determine the account // ids where are a member of managed groups. See: oidc and ldap managed groups diff --git a/internal/authtoken/testing.go b/internal/authtoken/testing.go index 1748d8bd53..1956033b82 100644 --- a/internal/authtoken/testing.go +++ b/internal/authtoken/testing.go @@ -51,7 +51,7 @@ func TestAuthToken(t testing.TB, conn *db.DB, kms *kms.Kms, scopeId string, opt // TestRoleGrantsForToken contains information used by TestAuthTokenWithRoles to create // roles and their associated grants (with grant scopes) type TestRoleGrantsForToken struct { - RoleScopeID string + RoleScopeId string GrantStrings []string GrantScopes []string } @@ -75,7 +75,7 @@ func TestAuthTokenWithRoles(t testing.TB, conn *db.DB, kms *kms.Kms, scopeId str acct := password.TestAccount(t, conn, authMethod.GetPublicId(), loginName) user := iam.TestUser(t, iamRepo, scopeId, iam.WithAccountIds(acct.GetPublicId())) for _, r := range roles { - role := iam.TestRoleWithGrants(t, conn, r.RoleScopeID, r.GrantScopes, r.GrantStrings) + role := iam.TestRoleWithGrants(t, conn, r.RoleScopeId, r.GrantScopes, r.GrantStrings) _ = iam.TestUserRole(t, conn, role.PublicId, user.PublicId) } fullGrantToken, err := atRepo.CreateAuthToken(ctx, user, acct.GetPublicId()) diff --git a/internal/daemon/controller/handlers/accounts/grants_test.go b/internal/daemon/controller/handlers/accounts/grants_test.go index 53fc16ce8a..e144c2a38b 100644 --- a/internal/daemon/controller/handlers/accounts/grants_test.go +++ b/internal/daemon/controller/handlers/accounts/grants_test.go @@ -61,7 +61,7 @@ func TestListPassword_Grants(t *testing.T) { }, roleRequest: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: globals.GlobalPrefix, + RoleScopeId: globals.GlobalPrefix, GrantStrings: []string{"ids=*;type=*;actions=list,read"}, GrantScopes: []string{globals.GrantScopeChildren}, }, @@ -77,7 +77,7 @@ func TestListPassword_Grants(t *testing.T) { }, roleRequest: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: org.GetPublicId(), + RoleScopeId: org.GetPublicId(), GrantStrings: []string{"ids=*;type=*;actions=list,read"}, GrantScopes: []string{globals.GrantScopeChildren}, }, diff --git a/internal/daemon/controller/handlers/aliases/grants_test.go b/internal/daemon/controller/handlers/aliases/grants_test.go index 81973163df..a0c496a6af 100644 --- a/internal/daemon/controller/handlers/aliases/grants_test.go +++ b/internal/daemon/controller/handlers/aliases/grants_test.go @@ -61,7 +61,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: globals.GlobalPrefix, + RoleScopeId: globals.GlobalPrefix, GrantStrings: []string{"ids=*;type=alias;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis}, }, @@ -77,7 +77,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: globals.GlobalPrefix, + RoleScopeId: globals.GlobalPrefix, GrantStrings: []string{"ids=*;type=group;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis}, }, diff --git a/internal/daemon/controller/handlers/authmethods/grants_test.go b/internal/daemon/controller/handlers/authmethods/grants_test.go index 02ebd550cc..219a858817 100644 --- a/internal/daemon/controller/handlers/authmethods/grants_test.go +++ b/internal/daemon/controller/handlers/authmethods/grants_test.go @@ -100,7 +100,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: globals.GlobalPrefix, + RoleScopeId: globals.GlobalPrefix, GrantStrings: []string{"ids=*;type=auth-method;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, }, @@ -149,7 +149,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: org1.PublicId, + RoleScopeId: org1.PublicId, GrantStrings: []string{"ids=*;type=auth-method;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, }, @@ -205,7 +205,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: globals.GlobalPrefix, + RoleScopeId: globals.GlobalPrefix, GrantStrings: []string{"ids=*;type=auth-method;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, }, @@ -219,7 +219,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: globals.GlobalPrefix, + RoleScopeId: globals.GlobalPrefix, GrantStrings: []string{"ids=*;type=auth-method;actions=list,read"}, GrantScopes: []string{globals.GrantScopeChildren}, }, diff --git a/internal/daemon/controller/handlers/authtokens/grants_test.go b/internal/daemon/controller/handlers/authtokens/grants_test.go index 2394c0b39f..8eb9305964 100644 --- a/internal/daemon/controller/handlers/authtokens/grants_test.go +++ b/internal/daemon/controller/handlers/authtokens/grants_test.go @@ -79,7 +79,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: globals.GlobalPrefix, + RoleScopeId: globals.GlobalPrefix, GrantStrings: []string{"ids=*;type=auth-token;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, }, @@ -95,7 +95,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: org1.PublicId, + RoleScopeId: org1.PublicId, GrantStrings: []string{"ids=*;type=auth-token;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, }, diff --git a/internal/daemon/controller/handlers/credentiallibraries/grants_test.go b/internal/daemon/controller/handlers/credentiallibraries/grants_test.go index 7e4a12ff0b..d9ec3e593c 100644 --- a/internal/daemon/controller/handlers/credentiallibraries/grants_test.go +++ b/internal/daemon/controller/handlers/credentiallibraries/grants_test.go @@ -58,7 +58,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: globals.GlobalPrefix, + RoleScopeId: globals.GlobalPrefix, GrantStrings: []string{"ids=*;type=credential-library;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeDescendants}, }, @@ -73,7 +73,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: org.GetPublicId(), + RoleScopeId: org.GetPublicId(), GrantStrings: []string{"ids=*;type=credential-library;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, }, diff --git a/internal/daemon/controller/handlers/credentials/grants_test.go b/internal/daemon/controller/handlers/credentials/grants_test.go index 2799d0d3e8..846f760807 100644 --- a/internal/daemon/controller/handlers/credentials/grants_test.go +++ b/internal/daemon/controller/handlers/credentials/grants_test.go @@ -78,7 +78,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: globals.GlobalPrefix, + RoleScopeId: globals.GlobalPrefix, GrantStrings: []string{"ids=*;type=credential;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeDescendants}, }, @@ -93,7 +93,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: org.PublicId, + RoleScopeId: org.PublicId, GrantStrings: []string{"ids=*;type=credential;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, }, @@ -108,7 +108,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: proj.PublicId, + RoleScopeId: proj.PublicId, GrantStrings: []string{"ids=*;type=credential;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis}, }, diff --git a/internal/daemon/controller/handlers/credentialstores/grants_test.go b/internal/daemon/controller/handlers/credentialstores/grants_test.go index 5ff3f50525..880dc922da 100644 --- a/internal/daemon/controller/handlers/credentialstores/grants_test.go +++ b/internal/daemon/controller/handlers/credentialstores/grants_test.go @@ -87,7 +87,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: globals.GlobalPrefix, + RoleScopeId: globals.GlobalPrefix, GrantStrings: []string{"ids=*;type=credential-store;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeDescendants}, }, @@ -103,7 +103,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: org.PublicId, + RoleScopeId: org.PublicId, GrantStrings: []string{"ids=*;type=credential-store;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, }, @@ -118,7 +118,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: proj.PublicId, + RoleScopeId: proj.PublicId, GrantStrings: []string{"ids=*;type=credential-store;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis}, }, diff --git a/internal/daemon/controller/handlers/groups/grants_test.go b/internal/daemon/controller/handlers/groups/grants_test.go index d0daddb6e2..a42d491ac2 100644 --- a/internal/daemon/controller/handlers/groups/grants_test.go +++ b/internal/daemon/controller/handlers/groups/grants_test.go @@ -5,17 +5,28 @@ package groups_test import ( "context" + "fmt" + "slices" "testing" "github.com/hashicorp/boundary/globals" + "github.com/hashicorp/boundary/internal/auth" + "github.com/hashicorp/boundary/internal/auth/ldap" + "github.com/hashicorp/boundary/internal/auth/oidc" + "github.com/hashicorp/boundary/internal/auth/password" "github.com/hashicorp/boundary/internal/authtoken" - "github.com/hashicorp/boundary/internal/daemon/controller/auth" + cauth "github.com/hashicorp/boundary/internal/daemon/controller/auth" + "github.com/hashicorp/boundary/internal/daemon/controller/handlers" "github.com/hashicorp/boundary/internal/daemon/controller/handlers/groups" "github.com/hashicorp/boundary/internal/db" pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services" "github.com/hashicorp/boundary/internal/iam" "github.com/hashicorp/boundary/internal/kms" + pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/groups" + "github.com/hashicorp/go-uuid" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/types/known/fieldmaskpb" + "google.golang.org/protobuf/types/known/wrapperspb" ) // TestGrants_ReadActions tests read actions to assert that grants are being applied properly @@ -38,70 +49,196 @@ import ( func TestGrants_ReadActions(t *testing.T) { ctx := context.Background() conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) wrap := db.TestWrapper(t) iamRepo := iam.TestRepo(t, conn, wrap) + kmsCache := kms.TestKms(t, conn, wrap) + atRepo, err := authtoken.NewRepository(ctx, rw, rw, kmsCache) + require.NoError(t, err) + repoFn := func() (*iam.Repository, error) { return iamRepo, nil } - kmsCache := kms.TestKms(t, conn, wrap) s, err := groups.NewService(ctx, repoFn, 1000) require.NoError(t, err) - org1, _ := iam.TestScopes(t, iamRepo) + org1, proj1 := iam.TestScopes(t, iamRepo) org2, proj2 := iam.TestScopes(t, iamRepo) - proj3 := iam.TestProject(t, iamRepo, org2.PublicId) + proj3 := iam.TestProject(t, iamRepo, org2.GetPublicId()) + globalGroup := iam.TestGroup(t, conn, globals.GlobalPrefix, iam.WithDescription("global"), iam.WithName("global")) org1Group := iam.TestGroup(t, conn, org1.GetPublicId(), iam.WithDescription("org1"), iam.WithName("org1")) org2Group := iam.TestGroup(t, conn, org2.GetPublicId(), iam.WithDescription("org2"), iam.WithName("org2")) - + proj1Group := iam.TestGroup(t, conn, proj1.GetPublicId(), iam.WithDescription("proj1"), iam.WithName("proj1")) proj2Group := iam.TestGroup(t, conn, proj2.GetPublicId(), iam.WithDescription("proj2"), iam.WithName("proj2")) proj3Group := iam.TestGroup(t, conn, proj3.GetPublicId(), iam.WithDescription("proj3"), iam.WithName("proj3")) t.Run("List", func(t *testing.T) { testcases := []struct { - name string - input *pbs.ListGroupsRequest - rolesToCreate []authtoken.TestRoleGrantsForToken - wantErr error - wantIDs []string + name string + input *pbs.ListGroupsRequest + userFunc func() (*iam.User, auth.Account) + wantErr error + wantIDs []string }{ { - name: "global role grant this and children returns global and org groups", + name: "global role grant this only returns in global groups", + wantErr: nil, input: &pbs.ListGroupsRequest{ ScopeId: globals.GlobalPrefix, Recursive: true, }, - rolesToCreate: []authtoken.TestRoleGrantsForToken{ + userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ { - RoleScopeID: globals.GlobalPrefix, - GrantStrings: []string{"ids=*;type=group;actions=list,read"}, - GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeThis}, }, + }), + wantIDs: []string{globalGroup.PublicId}, + }, + { + name: "global role grant this and children returns global and org groups", + input: &pbs.ListGroupsRequest{ + ScopeId: globals.GlobalPrefix, + Recursive: true, }, + userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=list,read"}, + GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, + }, + }), wantErr: nil, wantIDs: []string{globalGroup.PublicId, org1Group.PublicId, org2Group.PublicId}, }, { - name: "org role grant this and children returns org and project groups", + name: "global role grant via managed groups this and children returns org and proj groups", input: &pbs.ListGroupsRequest{ - ScopeId: org2.PublicId, + ScopeId: org1.PublicId, + Recursive: true, + }, + userFunc: iam.TestUserManagedGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, oidc.TestAuthMethodWithAccountInManagedGroup, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: org1.PublicId, + Grants: []string{"ids=*;type=group;actions=list,read"}, + GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, + }, + }), + wantErr: nil, + wantIDs: []string{org1Group.PublicId, proj1Group.PublicId}, + }, + { + name: "global role grant this and descendant returns all groups", + input: &pbs.ListGroupsRequest{ + ScopeId: globals.GlobalPrefix, Recursive: true, }, - rolesToCreate: []authtoken.TestRoleGrantsForToken{ + userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ { - RoleScopeID: org2.PublicId, - GrantStrings: []string{"ids=*;type=group;actions=list,read"}, - GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeDescendants}, }, + }), + wantErr: nil, + wantIDs: []string{globalGroup.PublicId, org1Group.PublicId, org2Group.PublicId, proj1Group.PublicId, proj2Group.PublicId, proj3Group.PublicId}, + }, + { + name: "org role grant children IDs only org children", + input: &pbs.ListGroupsRequest{ + ScopeId: org2.PublicId, + Recursive: true, }, + userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeDescendants}, + }, + }), wantErr: nil, wantIDs: []string{org2Group.PublicId, proj2Group.PublicId, proj3Group.PublicId}, }, + { + name: "LDAP org role grant children IDs only org children", + input: &pbs.ListGroupsRequest{ + ScopeId: org2.PublicId, + Recursive: true, + }, + userFunc: iam.TestUserManagedGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, ldap.TestAuthMethodWithAccountInManagedGroup, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: org2.PublicId, + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + wantErr: nil, + wantIDs: []string{org2Group.PublicId}, + }, + { + name: "no list permission returns error", + input: &pbs.ListGroupsRequest{ + ScopeId: globals.GlobalPrefix, + Recursive: true, + }, + userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{ + fmt.Sprintf("ids=%s;types=group;actions=read", proj1Group.PublicId), + }, + GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeDescendants}, + }, + }), + wantErr: handlers.ForbiddenError(), + wantIDs: nil, + }, + { + name: "global role scope specific grants only returns granted scopes", + input: &pbs.ListGroupsRequest{ + ScopeId: globals.GlobalPrefix, + Recursive: true, + }, + userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=read,list"}, + GrantScopes: []string{proj1.PublicId, proj2.PublicId, proj3.PublicId}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=read,list"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + wantErr: nil, + wantIDs: []string{globalGroup.PublicId, proj1Group.PublicId, proj2Group.PublicId, proj3Group.PublicId}, + }, + { + name: "global role not granted group resources returns error", + input: &pbs.ListGroupsRequest{ + ScopeId: globals.GlobalPrefix, + Recursive: true, + }, + userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=target;actions=read,list"}, + GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeDescendants}, + }, + }), + wantErr: handlers.ForbiddenError(), + wantIDs: nil, + }, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { - tok := authtoken.TestAuthTokenWithRoles(t, conn, kmsCache, globals.GlobalPrefix, tc.rolesToCreate) - fullGrantAuthCtx := auth.TestAuthContextFromToken(t, conn, wrap, tok, iamRepo) + user, account := tc.userFunc() + tok, err := atRepo.CreateAuthToken(ctx, user, account.GetPublicId()) + require.NoError(t, err) + fullGrantAuthCtx := cauth.TestAuthContextFromToken(t, conn, wrap, tok, iamRepo) got, finalErr := s.ListGroups(fullGrantAuthCtx, tc.input) if tc.wantErr != nil { require.ErrorIs(t, finalErr, tc.wantErr) @@ -116,4 +253,1948 @@ func TestGrants_ReadActions(t *testing.T) { }) } }) + + t.Run("Get", func(t *testing.T) { + testcases := []struct { + name string + userFunc func() (*iam.User, auth.Account) + inputWantErrMap map[*pbs.GetGroupRequest]error + }{ + { + name: "global role group grant this scope with all permissions", + userFunc: iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + inputWantErrMap: map[*pbs.GetGroupRequest]error{ + {Id: globalGroup.PublicId}: nil, + {Id: org1Group.PublicId}: handlers.ForbiddenError(), + {Id: proj1Group.PublicId}: handlers.ForbiddenError(), + {Id: org2Group.PublicId}: handlers.ForbiddenError(), + {Id: proj2Group.PublicId}: handlers.ForbiddenError(), + }, + }, + { + name: "global role group grant this scope with all permissions", + userFunc: iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + inputWantErrMap: map[*pbs.GetGroupRequest]error{ + {Id: globalGroup.PublicId}: nil, + {Id: org1Group.PublicId}: handlers.ForbiddenError(), + {Id: proj1Group.PublicId}: handlers.ForbiddenError(), + {Id: org2Group.PublicId}: handlers.ForbiddenError(), + {Id: proj2Group.PublicId}: handlers.ForbiddenError(), + }, + }, + { + name: "global role grant children scopes with all permissions", + userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeChildren}, + }, + }), + inputWantErrMap: map[*pbs.GetGroupRequest]error{ + {Id: globalGroup.PublicId}: handlers.ForbiddenError(), + {Id: org1Group.PublicId}: nil, + {Id: proj1Group.PublicId}: handlers.ForbiddenError(), + {Id: org2Group.PublicId}: nil, + {Id: proj2Group.PublicId}: handlers.ForbiddenError(), + }, + }, + { + name: "global role grant descendant scopes with all permissions", + userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeDescendants}, + }, + }), + inputWantErrMap: map[*pbs.GetGroupRequest]error{ + {Id: globalGroup.PublicId}: handlers.ForbiddenError(), + {Id: org1Group.PublicId}: nil, + {Id: proj1Group.PublicId}: nil, + {Id: org2Group.PublicId}: nil, + {Id: proj2Group.PublicId}: nil, + }, + }, + { + name: "global role grant this and children scopes with all permissions", + userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, + }, + }), + inputWantErrMap: map[*pbs.GetGroupRequest]error{ + {Id: globalGroup.PublicId}: nil, + {Id: org1Group.PublicId}: nil, + {Id: proj1Group.PublicId}: handlers.ForbiddenError(), + {Id: org2Group.PublicId}: nil, + {Id: proj2Group.PublicId}: handlers.ForbiddenError(), + }, + }, + { + name: "global role grant this and descendant scopes with all permissions", + userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeDescendants}, + }, + }), + inputWantErrMap: map[*pbs.GetGroupRequest]error{ + {Id: globalGroup.PublicId}: nil, + {Id: org1Group.PublicId}: nil, + {Id: proj1Group.PublicId}: nil, + {Id: org2Group.PublicId}: nil, + {Id: proj2Group.PublicId}: nil, + }, + }, + { + name: "org1 role grant this scope with all permissions", + userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: org1.GetPublicId(), + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + inputWantErrMap: map[*pbs.GetGroupRequest]error{ + {Id: globalGroup.PublicId}: handlers.ForbiddenError(), + {Id: org1Group.PublicId}: nil, + {Id: proj1Group.PublicId}: handlers.ForbiddenError(), + {Id: org2Group.PublicId}: handlers.ForbiddenError(), + {Id: proj2Group.PublicId}: handlers.ForbiddenError(), + }, + }, + { + name: "org1 role grant children scope with all permissions", + userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: org1.GetPublicId(), + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeChildren}, + }, + }), + inputWantErrMap: map[*pbs.GetGroupRequest]error{ + {Id: globalGroup.PublicId}: handlers.ForbiddenError(), + {Id: org1Group.PublicId}: handlers.ForbiddenError(), + {Id: proj1Group.PublicId}: nil, + {Id: org2Group.PublicId}: handlers.ForbiddenError(), + {Id: proj2Group.PublicId}: handlers.ForbiddenError(), + }, + }, + { + name: "org1 role grant this and children scopes with all permissions", + userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: org1.GetPublicId(), + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, + }, + }), + inputWantErrMap: map[*pbs.GetGroupRequest]error{ + {Id: globalGroup.PublicId}: handlers.ForbiddenError(), + {Id: org1Group.PublicId}: nil, + {Id: proj1Group.PublicId}: nil, + {Id: org2Group.PublicId}: handlers.ForbiddenError(), + {Id: proj2Group.PublicId}: handlers.ForbiddenError(), + }, + }, + { + name: "proj1 role grant this scope with all permissions", + userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: proj1.GetPublicId(), + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + inputWantErrMap: map[*pbs.GetGroupRequest]error{ + {Id: globalGroup.PublicId}: handlers.ForbiddenError(), + {Id: org1Group.PublicId}: handlers.ForbiddenError(), + {Id: proj1Group.PublicId}: nil, + {Id: org2Group.PublicId}: handlers.ForbiddenError(), + {Id: proj2Group.PublicId}: handlers.ForbiddenError(), + }, + }, + { + name: "global role grant this and descendant scope with read permissions on specific group", + userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{fmt.Sprintf("ids=%s;types=group ;actions=read", org1Group.PublicId)}, + GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeDescendants}, + }, + }), + inputWantErrMap: map[*pbs.GetGroupRequest]error{ + {Id: globalGroup.PublicId}: handlers.ForbiddenError(), + {Id: org1Group.PublicId}: nil, + {Id: proj1Group.PublicId}: handlers.ForbiddenError(), + {Id: org2Group.PublicId}: handlers.ForbiddenError(), + {Id: proj2Group.PublicId}: handlers.ForbiddenError(), + }, + }, + { + name: "global role grant this and specific scopes with read permissions on specific group", + userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{ + fmt.Sprintf("ids=%s;types=group;actions=read", org1Group.PublicId), + fmt.Sprintf("ids=%s;types=group;actions=read", proj1Group.PublicId), + }, + GrantScopes: []string{org1.PublicId, proj1.PublicId}, + }, + }), + inputWantErrMap: map[*pbs.GetGroupRequest]error{ + {Id: globalGroup.PublicId}: handlers.ForbiddenError(), + {Id: org1Group.PublicId}: nil, + {Id: proj1Group.PublicId}: nil, + {Id: org2Group.PublicId}: handlers.ForbiddenError(), + {Id: proj2Group.PublicId}: handlers.ForbiddenError(), + }, + }, + { + name: "union multiple role grant specific resources permissions", + userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{ + fmt.Sprintf("ids=%s;types=group;actions=read", globalGroup.PublicId), + }, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: org1.GetPublicId(), + Grants: []string{ + fmt.Sprintf("ids=%s;types=group;actions=read", org1Group.PublicId), + fmt.Sprintf("ids=%s;types=group;actions=read", proj1Group.PublicId), + }, + GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, + }, + }), + inputWantErrMap: map[*pbs.GetGroupRequest]error{ + {Id: globalGroup.PublicId}: nil, + {Id: org1Group.PublicId}: nil, + {Id: proj1Group.PublicId}: nil, + {Id: org2Group.PublicId}: handlers.ForbiddenError(), + {Id: proj2Group.PublicId}: handlers.ForbiddenError(), + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + user, account := tc.userFunc() + tok, err := atRepo.CreateAuthToken(ctx, user, account.GetPublicId()) + require.NoError(t, err) + fullGrantAuthCtx := cauth.TestAuthContextFromToken(t, conn, wrap, tok, iamRepo) + for input, wantErr := range tc.inputWantErrMap { + _, err := s.GetGroup(fullGrantAuthCtx, input) + // not found means expect error + if wantErr != nil { + require.ErrorIs(t, err, wantErr) + continue + } + require.NoError(t, err) + } + }) + } + }) +} + +// TestGrants_WriteActions tests write actions to assert that grants are being applied properly +// +// [create, update, delete] +// Role - which scope the role is created in +// - global level +// - org level +// - project level +// Grant - what IAM grant scope is set for the permission +// - global: descendant +// - org: children +// - project +// Scopes [resource]: +// - global [globalGroup] +// - org1 [org1Group] +// - proj1 [proj1Group] +// - org2 [org2Group] +// - proj2 [proj2Group] +// - proj3 [proj3Group] +func TestGrants_WriteActions(t *testing.T) { + t.Run("create", func(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrap := db.TestWrapper(t) + kmsCache := kms.TestKms(t, conn, wrap) + iamRepo := iam.TestRepo(t, conn, wrap) + atRepo, err := authtoken.NewRepository(ctx, rw, rw, kmsCache) + require.NoError(t, err) + repoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + s, err := groups.NewService(ctx, repoFn, 1000) + require.NoError(t, err) + + org1, proj1 := iam.TestScopes(t, iamRepo) + org2, proj2 := iam.TestScopes(t, iamRepo) + proj3 := iam.TestProject(t, iamRepo, org2.GetPublicId()) + + testcases := []struct { + name string + userFunc func() (*iam.User, auth.Account) + canCreateInScopes map[*pbs.CreateGroupRequest]error + }{ + { + name: "direct grant all can create all", + userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeDescendants}, + }, + }), + canCreateInScopes: map[*pbs.CreateGroupRequest]error{ + {Item: &pb.Group{ScopeId: globals.GlobalPrefix}}: nil, + {Item: &pb.Group{ScopeId: org1.PublicId}}: nil, + {Item: &pb.Group{ScopeId: org2.PublicId}}: nil, + {Item: &pb.Group{ScopeId: proj1.PublicId}}: nil, + {Item: &pb.Group{ScopeId: proj2.PublicId}}: nil, + {Item: &pb.Group{ScopeId: proj3.PublicId}}: nil, + }, + }, + { + name: "groups grant all can create all", + userFunc: iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeDescendants}, + }, + }), + canCreateInScopes: map[*pbs.CreateGroupRequest]error{ + {Item: &pb.Group{ScopeId: globals.GlobalPrefix}}: nil, + {Item: &pb.Group{ScopeId: org1.PublicId}}: nil, + {Item: &pb.Group{ScopeId: org2.PublicId}}: nil, + {Item: &pb.Group{ScopeId: proj1.PublicId}}: nil, + {Item: &pb.Group{ScopeId: proj2.PublicId}}: nil, + {Item: &pb.Group{ScopeId: proj3.PublicId}}: nil, + }, + }, + { + name: "ldap grant all can create all", + userFunc: iam.TestUserManagedGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, ldap.TestAuthMethodWithAccountInManagedGroup, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeDescendants}, + }, + }), + canCreateInScopes: map[*pbs.CreateGroupRequest]error{ + {Item: &pb.Group{ScopeId: globals.GlobalPrefix}}: nil, + {Item: &pb.Group{ScopeId: org1.PublicId}}: nil, + {Item: &pb.Group{ScopeId: org2.PublicId}}: nil, + {Item: &pb.Group{ScopeId: proj1.PublicId}}: nil, + {Item: &pb.Group{ScopeId: proj2.PublicId}}: nil, + {Item: &pb.Group{ScopeId: proj3.PublicId}}: nil, + }, + }, + { + name: "oidc grant all can create all", + userFunc: iam.TestUserManagedGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, oidc.TestAuthMethodWithAccountInManagedGroup, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeDescendants}, + }, + }), + canCreateInScopes: map[*pbs.CreateGroupRequest]error{ + {Item: &pb.Group{ScopeId: globals.GlobalPrefix}}: nil, + {Item: &pb.Group{ScopeId: org1.PublicId}}: nil, + {Item: &pb.Group{ScopeId: org2.PublicId}}: nil, + {Item: &pb.Group{ScopeId: proj1.PublicId}}: nil, + {Item: &pb.Group{ScopeId: proj2.PublicId}}: nil, + {Item: &pb.Group{ScopeId: proj3.PublicId}}: nil, + }, + }, + { + name: "grant children can only create in orgs", + userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeChildren}, + }, + }), + canCreateInScopes: map[*pbs.CreateGroupRequest]error{ + {Item: &pb.Group{ScopeId: globals.GlobalPrefix}}: handlers.ForbiddenError(), + {Item: &pb.Group{ScopeId: org1.PublicId}}: nil, + {Item: &pb.Group{ScopeId: org2.PublicId}}: nil, + {Item: &pb.Group{ScopeId: proj1.PublicId}}: handlers.ForbiddenError(), + {Item: &pb.Group{ScopeId: proj2.PublicId}}: handlers.ForbiddenError(), + {Item: &pb.Group{ScopeId: proj3.PublicId}}: handlers.ForbiddenError(), + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + user, account := tc.userFunc() + tok, err := atRepo.CreateAuthToken(ctx, user, account.GetPublicId()) + require.NoError(t, err) + fullGrantAuthCtx := cauth.TestAuthContextFromToken(t, conn, wrap, tok, iamRepo) + + for req, wantErr := range tc.canCreateInScopes { + _, err := s.CreateGroup(fullGrantAuthCtx, req) + if wantErr != nil { + require.ErrorIs(t, err, wantErr) + continue + } + require.NoError(t, err) + } + }) + } + }) + t.Run("delete", func(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) + kmsCache := kms.TestKms(t, conn, wrap) + repoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + atRepo, err := authtoken.NewRepository(ctx, rw, rw, kmsCache) + require.NoError(t, err) + + s, err := groups.NewService(ctx, repoFn, 1000) + require.NoError(t, err) + + org1, proj1 := iam.TestScopes(t, iamRepo) + org2, proj2 := iam.TestScopes(t, iamRepo) + proj3 := iam.TestProject(t, iamRepo, org2.GetPublicId()) + + allScopeIds := []string{globals.GlobalPrefix, org1.PublicId, org2.PublicId, proj1.PublicId, proj2.PublicId, proj3.PublicId} + testcases := []struct { + name string + userFunc func() (*iam.User, auth.Account) + deleteAllowedAtScopeIds []string + }{ + { + name: "grant all can delete all", + userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeDescendants}, + }, + }), + deleteAllowedAtScopeIds: allScopeIds, + }, + { + name: "grant children can only delete in orgs", + userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeChildren}, + }, + }), + deleteAllowedAtScopeIds: []string{org1.PublicId, org2.PublicId}, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + // setup a map to track which scope correlates to a group + scopeIdGroupMap := map[string]*iam.Group{} + for _, scp := range allScopeIds { + g := iam.TestGroup(t, conn, scp) + scopeIdGroupMap[scp] = g + } + user, account := tc.userFunc() + tok, err := atRepo.CreateAuthToken(ctx, user, account.GetPublicId()) + require.NoError(t, err) + fullGrantAuthCtx := cauth.TestAuthContextFromToken(t, conn, wrap, tok, iamRepo) + for scope, group := range scopeIdGroupMap { + _, err = s.DeleteGroup(fullGrantAuthCtx, &pbs.DeleteGroupRequest{Id: group.PublicId}) + if !slices.Contains(tc.deleteAllowedAtScopeIds, scope) { + require.ErrorIs(t, err, handlers.ForbiddenError()) + continue + } + require.NoErrorf(t, err, "failed to delete group in scope %s", scope) + } + }) + } + }) + + t.Run("update", func(t *testing.T) { + testcases := []struct { + name string + setupScopesResourcesAndUser func(t *testing.T, conn *db.DB, iamRepo *iam.Repository, kmsCache *kms.Kms) (*iam.Group, func() (*iam.User, auth.Account)) + wantErr error + }{ + { + name: "global_scope_group_good_grant_success", + setupScopesResourcesAndUser: func(t *testing.T, conn *db.DB, iamRepo *iam.Repository, kmsCache *kms.Kms) (*iam.Group, func() (*iam.User, auth.Account)) { + g := iam.TestGroup(t, conn, globals.GlobalPrefix) + return g, iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }) + }, + wantErr: nil, + }, + { + name: "grant specific scope success", + setupScopesResourcesAndUser: func(t *testing.T, conn *db.DB, iamRepo *iam.Repository, kmsCache *kms.Kms) (*iam.Group, func() (*iam.User, auth.Account)) { + _, proj := iam.TestScopes(t, iamRepo) + g := iam.TestGroup(t, conn, proj.PublicId) + return g, iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{proj.PublicId}, + }, + }) + }, + wantErr: nil, + }, + { + name: "grant specific resource and scope success", + setupScopesResourcesAndUser: func(t *testing.T, conn *db.DB, iamRepo *iam.Repository, kmsCache *kms.Kms) (*iam.Group, func() (*iam.User, auth.Account)) { + _, proj := iam.TestScopes(t, iamRepo) + g := iam.TestGroup(t, conn, proj.PublicId) + return g, iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{fmt.Sprintf("ids=%s;types=group;actions=*", g.PublicId)}, + GrantScopes: []string{proj.PublicId}, + }, + }) + }, + wantErr: nil, + }, + { + name: "no grant fails update", + setupScopesResourcesAndUser: func(t *testing.T, conn *db.DB, iamRepo *iam.Repository, kmsCache *kms.Kms) (*iam.Group, func() (*iam.User, auth.Account)) { + g := iam.TestGroup(t, conn, globals.GlobalPrefix) + return g, iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeChildren}, + }, + }) + }, + wantErr: handlers.ForbiddenError(), + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) + kmsCache := kms.TestKms(t, conn, wrap) + atRepo, err := authtoken.NewRepository(ctx, rw, rw, kmsCache) + require.NoError(t, err) + repoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + s, err := groups.NewService(ctx, repoFn, 1000) + require.NoError(t, err) + original, userFunc := tc.setupScopesResourcesAndUser(t, conn, iamRepo, kmsCache) + user, account := userFunc() + tok, err := atRepo.CreateAuthToken(ctx, user, account.GetPublicId()) + require.NoError(t, err) + fullGrantAuthCtx := cauth.TestAuthContextFromToken(t, conn, wrap, tok, iamRepo) + got, err := s.UpdateGroup(fullGrantAuthCtx, &pbs.UpdateGroupRequest{ + Id: original.PublicId, + Item: &pb.Group{ + Name: &wrapperspb.StringValue{Value: "new-name"}, + Description: &wrapperspb.StringValue{Value: "new-description"}, + Version: 1, + }, + UpdateMask: &fieldmaskpb.FieldMask{ + Paths: []string{"name", "description"}, + }, + }) + if tc.wantErr != nil { + require.Error(t, err) + require.ErrorIs(t, err, tc.wantErr) + return + } + require.NoError(t, err) + require.Equal(t, uint32(2), got.Item.Version) + require.True(t, got.Item.UpdatedTime.AsTime().After(original.UpdateTime.AsTime())) + }) + } + }) +} + +// TestGrants_ChildResourcesActions tests actions performed on the group-members (child-resources) +func TestGrants_ChildResourcesActions(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) + kmsCache := kms.TestKms(t, conn, wrap) + repoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + atRepo, err := authtoken.NewRepository(ctx, rw, rw, kmsCache) + require.NoError(t, err) + s, err := groups.NewService(ctx, repoFn, 1000) + require.NoError(t, err) + + org1, _ := iam.TestScopes(t, iamRepo) + org2, proj2 := iam.TestScopes(t, iamRepo) + proj3 := iam.TestProject(t, iamRepo, org2.GetPublicId()) + + globalUsers := []*iam.User{iam.TestUser(t, iamRepo, globals.GlobalPrefix), iam.TestUser(t, iamRepo, globals.GlobalPrefix)} + org1Users := []*iam.User{iam.TestUser(t, iamRepo, org1.PublicId), iam.TestUser(t, iamRepo, org1.PublicId)} + org2Users := []*iam.User{iam.TestUser(t, iamRepo, org2.PublicId), iam.TestUser(t, iamRepo, org2.PublicId)} + + type groupGetter interface { + GetItem() *pb.Group + } + + type testActionResult struct { + action func(context.Context, *iam.Group) (groupGetter, error) + wantErr error + } + + testcases := []struct { + name string + userFunc func() *iam.User + setupGroupAndRole func(t *testing.T) (*iam.Group, func() (*iam.User, auth.Account)) + // collection of actions to be executed in the tests in order, *iam.Group returned from each action which + // gets passed to the next action as parameter to preserve information such as `version` increments + actions []testActionResult + }{ + { + name: "all actions valid grant success", + + setupGroupAndRole: func(t *testing.T) (*iam.Group, func() (*iam.User, auth.Account)) { + group := iam.TestGroup(t, conn, globals.GlobalPrefix) + return group, iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }) + }, + actions: []testActionResult{ + { + action: func(authCtx context.Context, g *iam.Group) (groupGetter, error) { + out, err := s.AddGroupMembers(authCtx, &pbs.AddGroupMembersRequest{ + Id: g.PublicId, + Version: g.Version, + MemberIds: userIDs(org1Users), + }) + return out, err + }, + wantErr: nil, + }, + { + action: func(authCtx context.Context, g *iam.Group) (groupGetter, error) { + out, err := s.SetGroupMembers(authCtx, &pbs.SetGroupMembersRequest{ + Id: g.PublicId, + Version: g.Version, + MemberIds: userIDs(globalUsers), + }) + return out, err + }, + wantErr: nil, + }, + { + action: func(authCtx context.Context, g *iam.Group) (groupGetter, error) { + out, err := s.RemoveGroupMembers(authCtx, &pbs.RemoveGroupMembersRequest{ + Id: g.PublicId, + Version: g.Version, + MemberIds: userIDs(globalUsers), + }) + return out, err + }, + wantErr: nil, + }, + }, + }, + { + name: "only add and set allowed fail to remove", + setupGroupAndRole: func(t *testing.T) (*iam.Group, func() (*iam.User, auth.Account)) { + group := iam.TestGroup(t, conn, org1.PublicId) + return group, iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: org1.PublicId, + Grants: []string{"ids=*;type=*;actions=add-members"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: org1.PublicId, + Grants: []string{"ids=*;type=*;actions=set-members"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }) + }, + actions: []testActionResult{ + { + action: func(authCtx context.Context, g *iam.Group) (groupGetter, error) { + out, err := s.AddGroupMembers(authCtx, &pbs.AddGroupMembersRequest{ + Id: g.PublicId, + Version: g.Version, + MemberIds: userIDs(org1Users), + }) + return out, err + }, + wantErr: nil, + }, + { + action: func(authCtx context.Context, g *iam.Group) (groupGetter, error) { + out, err := s.SetGroupMembers(authCtx, &pbs.SetGroupMembersRequest{ + Id: g.PublicId, + Version: g.Version, + MemberIds: userIDs(org1Users), + }) + return out, err + }, + wantErr: nil, + }, + { + action: func(authCtx context.Context, g *iam.Group) (groupGetter, error) { + out, err := s.RemoveGroupMembers(authCtx, &pbs.RemoveGroupMembersRequest{ + Id: g.PublicId, + Version: g.Version, + MemberIds: userIDs(org1Users), + }) + return out, err + }, + wantErr: handlers.ForbiddenError(), + }, + }, + }, + { + name: "add_member_valid_specific_grant_success", + setupGroupAndRole: func(t *testing.T) (*iam.Group, func() (*iam.User, auth.Account)) { + group := iam.TestGroup(t, conn, org2.PublicId) + return group, iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: org2.PublicId, + Grants: []string{fmt.Sprintf("ids=%s;types=group;actions=add-members", group.PublicId)}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }) + }, + actions: []testActionResult{ + { + action: func(authCtx context.Context, g *iam.Group) (groupGetter, error) { + out, err := s.AddGroupMembers(authCtx, &pbs.AddGroupMembersRequest{ + Id: g.PublicId, + Version: g.Version, + MemberIds: userIDs(org2Users), + }) + return out, err + }, + wantErr: nil, + }, + }, + }, + { + name: "remove_member_valid_specific_grant_success", + setupGroupAndRole: func(t *testing.T) (*iam.Group, func() (*iam.User, auth.Account)) { + group := iam.TestGroup(t, conn, proj2.PublicId) + iam.TestGroupMember(t, conn, group.PublicId, org2Users[0].PublicId) + iam.TestGroupMember(t, conn, group.PublicId, org2Users[1].PublicId) + return group, iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{fmt.Sprintf("ids=%s;types=group;actions=remove-members", group.PublicId)}, + GrantScopes: []string{proj2.PublicId}, + }, + }) + }, + actions: []testActionResult{ + { + action: func(authCtx context.Context, g *iam.Group) (groupGetter, error) { + out, err := s.RemoveGroupMembers(authCtx, &pbs.RemoveGroupMembersRequest{ + Id: g.PublicId, + Version: g.Version, + MemberIds: userIDs(org2Users), + }) + return out, err + }, + wantErr: nil, + }, + }, + }, + { + name: "cross_scope_add_member_valid_specific_grant_success", + setupGroupAndRole: func(t *testing.T) (*iam.Group, func() (*iam.User, auth.Account)) { + group := iam.TestGroup(t, conn, proj3.PublicId) + return group, iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{fmt.Sprintf("ids=%s;types=group;actions=add-members", group.PublicId)}, + GrantScopes: []string{globals.GrantScopeDescendants}, + }, + }) + }, + actions: []testActionResult{ + { + action: func(authCtx context.Context, g *iam.Group) (groupGetter, error) { + users := userIDs(org1Users) + users = append(users, userIDs(org2Users)...) + out, err := s.AddGroupMembers(authCtx, &pbs.AddGroupMembersRequest{ + Id: g.PublicId, + Version: g.Version, + MemberIds: users, + }) + return out, err + }, + wantErr: nil, + }, + }, + }, + { + name: "add_member_with_valid_grant_string_invalid_scope_forbidden_error", + setupGroupAndRole: func(t *testing.T) (*iam.Group, func() (*iam.User, auth.Account)) { + group := iam.TestGroup(t, conn, org2.PublicId) + return group, iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=*;actions=*"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }) + }, + actions: []testActionResult{ + { + action: func(authCtx context.Context, g *iam.Group) (groupGetter, error) { + out, err := s.AddGroupMembers(authCtx, &pbs.AddGroupMembersRequest{ + Id: g.PublicId, + Version: g.Version, + MemberIds: userIDs(org2Users), + }) + return out, err + }, + wantErr: handlers.ForbiddenError(), + }, + }, + }, + { + name: "multiple_grants_success", + setupGroupAndRole: func(t *testing.T) (*iam.Group, func() (*iam.User, auth.Account)) { + group := iam.TestGroup(t, conn, proj2.PublicId) + return group, iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: proj2.PublicId, + Grants: []string{fmt.Sprintf("ids=%s;types=group;actions=add-members", group.PublicId)}, + GrantScopes: []string{proj2.PublicId}, + }, + { + RoleScopeId: proj2.PublicId, + Grants: []string{fmt.Sprintf("ids=%s;types=group;actions=set-members", group.PublicId)}, + GrantScopes: []string{proj2.PublicId}, + }, + { + RoleScopeId: proj2.PublicId, + Grants: []string{fmt.Sprintf("ids=%s;types=group;actions=remove-members", group.PublicId)}, + GrantScopes: []string{proj2.PublicId}, + }, + }) + }, + actions: []testActionResult{ + { + action: func(authCtx context.Context, g *iam.Group) (groupGetter, error) { + out, err := s.AddGroupMembers(authCtx, &pbs.AddGroupMembersRequest{ + Id: g.PublicId, + Version: g.Version, + MemberIds: userIDs(org2Users), + }) + return out, err + }, + wantErr: nil, + }, + { + action: func(authCtx context.Context, g *iam.Group) (groupGetter, error) { + out, err := s.SetGroupMembers(authCtx, &pbs.SetGroupMembersRequest{ + Id: g.PublicId, + Version: g.Version, + MemberIds: userIDs(org2Users), + }) + return out, err + }, + wantErr: nil, + }, + { + action: func(authCtx context.Context, g *iam.Group) (groupGetter, error) { + out, err := s.RemoveGroupMembers(authCtx, &pbs.RemoveGroupMembersRequest{ + Id: g.PublicId, + Version: g.Version, + MemberIds: userIDs(org2Users), + }) + return out, err + }, + wantErr: nil, + }, + }, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + group, userFn := tc.setupGroupAndRole(t) + user, account := userFn() + tok, err := atRepo.CreateAuthToken(ctx, user, account.GetPublicId()) + require.NoError(t, err) + fullGrantAuthCtx := cauth.TestAuthContextFromToken(t, conn, wrap, tok, iamRepo) + for _, act := range tc.actions { + out, err := act.action(fullGrantAuthCtx, group) + if act.wantErr != nil { + require.Error(t, err) + require.ErrorIs(t, err, act.wantErr) + continue + } + require.NoError(t, err) + // set version for future updates + group.Version = out.GetItem().Version + } + }) + } +} + +func TestOutputFields(t *testing.T) { + t.Run("ListGroups", func(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) + kmsCache := kms.TestKms(t, conn, wrap) + atRepo, err := authtoken.NewRepository(ctx, rw, rw, kmsCache) + require.NoError(t, err) + repoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + + globalGroup := iam.TestGroup(t, conn, globals.GlobalPrefix, iam.WithDescription("global"), iam.WithName("global")) + + org, proj := iam.TestScopes(t, iamRepo) + orgGroup := iam.TestGroup(t, conn, org.PublicId, iam.WithDescription("org"), iam.WithName("org")) + projGroup := iam.TestGroup(t, conn, proj.PublicId, iam.WithDescription("proj"), iam.WithName("proj")) + + globalUser := iam.TestUser(t, iamRepo, globals.GlobalPrefix) + orgUser := iam.TestUser(t, iamRepo, globals.GlobalPrefix) + projectUser := iam.TestUser(t, iamRepo, globals.GlobalPrefix) + + _ = iam.TestGroupMember(t, conn, globalGroup.PublicId, globalUser.PublicId) + _ = iam.TestGroupMember(t, conn, orgGroup.PublicId, orgUser.PublicId) + _ = iam.TestGroupMember(t, conn, projGroup.PublicId, projectUser.PublicId) + s, err := groups.NewService(ctx, repoFn, 1000) + require.NoError(t, err) + testcases := []struct { + name string + userFunc func() (*iam.User, auth.Account) + // keys are the group IDs | this also means 'id' is required in the outputfields for assertions to work properly + expectOutfields map[string][]string + }{ + { + name: "grants name, version, description", + userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=id,name,description"}, + GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeDescendants}, + }, + }), + expectOutfields: map[string][]string{ + globalGroup.PublicId: {globals.IdField, globals.NameField, globals.DescriptionField}, + orgGroup.PublicId: {globals.IdField, globals.NameField, globals.DescriptionField}, + projGroup.PublicId: {globals.IdField, globals.NameField, globals.DescriptionField}, + }, + }, + { + name: "grants scope, scopeId, authorized_actions", + userFunc: iam.TestUserManagedGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, ldap.TestAuthMethodWithAccountInManagedGroup, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=id,scope,scope_id,authorized_actions"}, + GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeDescendants}, + }, + }), + expectOutfields: map[string][]string{ + globalGroup.PublicId: {globals.IdField, globals.ScopeField, globals.ScopeIdField, globals.AuthorizedActionsField}, + orgGroup.PublicId: {globals.IdField, globals.ScopeField, globals.ScopeIdField, globals.AuthorizedActionsField}, + projGroup.PublicId: {globals.IdField, globals.ScopeField, globals.ScopeIdField, globals.AuthorizedActionsField}, + }, + }, + { + name: "grants update_time, create_time", + userFunc: iam.TestUserManagedGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, oidc.TestAuthMethodWithAccountInManagedGroup, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=id,updated_time,created_time,members,member_ids"}, + GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeDescendants}, + }, + }), + expectOutfields: map[string][]string{ + globalGroup.PublicId: {globals.IdField, globals.CreatedTimeField, globals.UpdatedTimeField}, + orgGroup.PublicId: {globals.IdField, globals.CreatedTimeField, globals.UpdatedTimeField}, + projGroup.PublicId: {globals.IdField, globals.CreatedTimeField, globals.UpdatedTimeField}, + }, + }, + { + name: "different output_fields for different scope", + userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=id,name,description"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=id,scope,scope_id,created_time,updated_time"}, + GrantScopes: []string{globals.GrantScopeChildren}, + }, + { + RoleScopeId: proj.PublicId, + Grants: []string{"ids=*;type=group;actions=*;output_fields=id,authorized_actions"}, + GrantScopes: []string{proj.PublicId}, + }, + }), + expectOutfields: map[string][]string{ + globalGroup.PublicId: {globals.IdField, globals.NameField, globals.DescriptionField}, + orgGroup.PublicId: {globals.IdField, globals.ScopeField, globals.ScopeIdField, globals.CreatedTimeField, globals.UpdatedTimeField}, + projGroup.PublicId: {globals.IdField, globals.AuthorizedActionsField}, + }, + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + user, account := tc.userFunc() + tok, err := atRepo.CreateAuthToken(ctx, user, account.GetPublicId()) + require.NoError(t, err) + fullGrantAuthCtx := cauth.TestAuthContextFromToken(t, conn, wrap, tok, iamRepo) + out, err := s.ListGroups(fullGrantAuthCtx, &pbs.ListGroupsRequest{ + ScopeId: globals.GlobalPrefix, + Recursive: true, + }) + require.NoError(t, err) + for _, item := range out.Items { + handlers.TestAssertOutputFields(t, item, tc.expectOutfields[item.Id]) + } + }) + } + }) + + t.Run("GetGroup", func(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) + kmsCache := kms.TestKms(t, conn, wrap) + atRepo, err := authtoken.NewRepository(ctx, rw, rw, kmsCache) + require.NoError(t, err) + repoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + globalGroupWithMember := iam.TestGroup(t, conn, globals.GlobalPrefix, iam.WithDescription("global"), iam.WithName("global")) + u := iam.TestUser(t, iamRepo, globals.GlobalPrefix) + _ = iam.TestGroupMember(t, conn, globalGroupWithMember.PublicId, u.PublicId) + s, err := groups.NewService(ctx, repoFn, 1000) + require.NoError(t, err) + + testcases := []struct { + name string + userFunc func() (*iam.User, auth.Account) + expectOutfields []string + }{ + { + name: "grants name and description", + userFunc: iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=read;output_fields=name,description"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.NameField, globals.DescriptionField}, + }, + { + name: "grants scope and scopeId", + userFunc: iam.TestUserManagedGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, ldap.TestAuthMethodWithAccountInManagedGroup, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=read;output_fields=scope,scope_id"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.ScopeField, globals.ScopeIdField}, + }, + { + name: "grants update_time and create_time", + userFunc: iam.TestUserManagedGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, oidc.TestAuthMethodWithAccountInManagedGroup, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=read;output_fields=updated_time,created_time"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.UpdatedTimeField, globals.CreatedTimeField}, + }, + { + name: "grants id, authorized_actions, version", + userFunc: iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=read;output_fields=id,authorized_actions,version"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.IdField, globals.AuthorizedActionsField, globals.VersionField}, + }, + { + name: "grants members, member_id", + userFunc: iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=read;output_fields=members,member_ids"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.MembersField, globals.MemberIdsField}, + }, + { + name: "composite grants id, authorized_actions, member_ids", + userFunc: iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=read;output_fields=id"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=read;output_fields=member_ids"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=read;output_fields=authorized_actions"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.IdField, globals.MemberIdsField, globals.AuthorizedActionsField}, + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + user, account := tc.userFunc() + tok, err := atRepo.CreateAuthToken(ctx, user, account.GetPublicId()) + require.NoError(t, err) + fullGrantAuthCtx := cauth.TestAuthContextFromToken(t, conn, wrap, tok, iamRepo) + out, err := s.GetGroup(fullGrantAuthCtx, &pbs.GetGroupRequest{Id: globalGroupWithMember.PublicId}) + require.NoError(t, err) + handlers.TestAssertOutputFields(t, out.Item, tc.expectOutfields) + }) + } + }) + t.Run("CreateGroup", func(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) + kmsCache := kms.TestKms(t, conn, wrap) + atRepo, err := authtoken.NewRepository(ctx, rw, rw, kmsCache) + require.NoError(t, err) + repoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + genUuid := func() string { + u, _ := uuid.GenerateUUID() + return u + } + + s, err := groups.NewService(ctx, repoFn, 1000) + require.NoError(t, err) + testcases := []struct { + name string + userFunc func() (*iam.User, auth.Account) + input *pbs.CreateGroupRequest + expectOutfields []string + }{ + { + name: "grants name and description", + input: &pbs.CreateGroupRequest{ + Item: &pb.Group{ + Name: &wrapperspb.StringValue{Value: genUuid()}, + Description: &wrapperspb.StringValue{Value: genUuid()}, + ScopeId: globals.GlobalPrefix, + }, + }, + userFunc: iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=name,description"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.NameField, globals.DescriptionField}, + }, + { + name: "grants scope and scopeId", + input: &pbs.CreateGroupRequest{ + Item: &pb.Group{ + Name: &wrapperspb.StringValue{Value: genUuid()}, + Description: &wrapperspb.StringValue{Value: genUuid()}, + ScopeId: globals.GlobalPrefix, + }, + }, + userFunc: iam.TestUserManagedGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, ldap.TestAuthMethodWithAccountInManagedGroup, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=scope,scope_id"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.ScopeField, globals.ScopeIdField}, + }, + { + name: "grants update_time and create_time", + input: &pbs.CreateGroupRequest{ + Item: &pb.Group{ + Name: &wrapperspb.StringValue{Value: genUuid()}, + Description: &wrapperspb.StringValue{Value: genUuid()}, + ScopeId: globals.GlobalPrefix, + }, + }, + userFunc: iam.TestUserManagedGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, oidc.TestAuthMethodWithAccountInManagedGroup, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=updated_time,created_time"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.UpdatedTimeField, globals.CreatedTimeField}, + }, + { + name: "grants id, authorized_actions, version", + input: &pbs.CreateGroupRequest{ + Item: &pb.Group{ + Name: &wrapperspb.StringValue{Value: genUuid()}, + Description: &wrapperspb.StringValue{Value: genUuid()}, + ScopeId: globals.GlobalPrefix, + }, + }, + userFunc: iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=id,authorized_actions,version"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.IdField, globals.AuthorizedActionsField, globals.VersionField}, + }, + { + name: "composite grants all fields", + input: &pbs.CreateGroupRequest{ + Item: &pb.Group{ + Name: &wrapperspb.StringValue{Value: genUuid()}, + Description: &wrapperspb.StringValue{Value: genUuid()}, + ScopeId: globals.GlobalPrefix, + }, + }, + userFunc: iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=id"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=scope"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=scope_id"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=name"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=description"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=created_time"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=authorized_actions"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=version"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{ + globals.IdField, + globals.ScopeField, + globals.ScopeIdField, + globals.NameField, + globals.DescriptionField, + globals.CreatedTimeField, + globals.AuthorizedActionsField, + globals.VersionField, + }, + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + user, account := tc.userFunc() + tok, err := atRepo.CreateAuthToken(ctx, user, account.GetPublicId()) + require.NoError(t, err) + fullGrantAuthCtx := cauth.TestAuthContextFromToken(t, conn, wrap, tok, iamRepo) + out, err := s.CreateGroup(fullGrantAuthCtx, tc.input) + require.NoError(t, err) + handlers.TestAssertOutputFields(t, out.Item, tc.expectOutfields) + }) + } + }) + + t.Run("UpdateGroup", func(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) + kmsCache := kms.TestKms(t, conn, wrap) + atRepo, err := authtoken.NewRepository(ctx, rw, rw, kmsCache) + require.NoError(t, err) + repoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + + // this can be used across test cases because we're only testing for output fields, not the update behaviors + inputFunc := func(t *testing.T) *pbs.UpdateGroupRequest { + name, _ := uuid.GenerateUUID() + desc, _ := uuid.GenerateUUID() + globalGroupWithMember := iam.TestGroup(t, conn, globals.GlobalPrefix, iam.WithDescription("global"), iam.WithName("global")) + u := iam.TestUser(t, iamRepo, globals.GlobalPrefix) + _ = iam.TestGroupMember(t, conn, globalGroupWithMember.PublicId, u.PublicId) + return &pbs.UpdateGroupRequest{ + Id: globalGroupWithMember.PublicId, + Item: &pb.Group{ + Name: &wrapperspb.StringValue{Value: name}, + Description: &wrapperspb.StringValue{Value: desc}, + Version: globalGroupWithMember.Version, + }, + UpdateMask: &fieldmaskpb.FieldMask{ + Paths: []string{"name", "description"}, + }, + } + } + + s, err := groups.NewService(ctx, repoFn, 1000) + require.NoError(t, err) + testcases := []struct { + name string + userFunc func() (*iam.User, auth.Account) + expectOutfields []string + }{ + { + name: "grants name and description", + userFunc: iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=name,description"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.NameField, globals.DescriptionField}, + }, + { + name: "grants scope and scopeId", + userFunc: iam.TestUserManagedGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, ldap.TestAuthMethodWithAccountInManagedGroup, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=scope,scope_id"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.ScopeField, globals.ScopeIdField}, + }, + { + name: "grants update_time and create_time", + + userFunc: iam.TestUserManagedGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, oidc.TestAuthMethodWithAccountInManagedGroup, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=updated_time,created_time"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.UpdatedTimeField, globals.CreatedTimeField}, + }, + { + name: "grants id, authorized_actions, version", + userFunc: iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=id,authorized_actions,version"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.IdField, globals.AuthorizedActionsField, globals.VersionField}, + }, + { + name: "composite grants all fields", + userFunc: iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=id"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=scope"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=scope_id"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=name"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=description"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=created_time"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=authorized_actions"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=version"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{ + globals.IdField, + globals.ScopeField, + globals.ScopeIdField, + globals.NameField, + globals.DescriptionField, + globals.CreatedTimeField, + globals.AuthorizedActionsField, + globals.VersionField, + }, + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + user, account := tc.userFunc() + tok, err := atRepo.CreateAuthToken(ctx, user, account.GetPublicId()) + require.NoError(t, err) + fullGrantAuthCtx := cauth.TestAuthContextFromToken(t, conn, wrap, tok, iamRepo) + out, err := s.UpdateGroup(fullGrantAuthCtx, inputFunc(t)) + require.NoError(t, err) + handlers.TestAssertOutputFields(t, out.Item, tc.expectOutfields) + }) + } + }) + + t.Run("AddGroupMembers", func(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) + kmsCache := kms.TestKms(t, conn, wrap) + atRepo, err := authtoken.NewRepository(ctx, rw, rw, kmsCache) + require.NoError(t, err) + repoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + + // this can be used across test cases because we're only testing for output fields, not the update behaviors + inputFunc := func(t *testing.T) *pbs.AddGroupMembersRequest { + name, _ := uuid.GenerateUUID() + desc, _ := uuid.GenerateUUID() + globalGroupWithMember := iam.TestGroup(t, conn, globals.GlobalPrefix, iam.WithDescription(desc), iam.WithName(name)) + u := iam.TestUser(t, iamRepo, globals.GlobalPrefix) + return &pbs.AddGroupMembersRequest{ + Id: globalGroupWithMember.PublicId, + Version: globalGroupWithMember.Version, + MemberIds: []string{u.PublicId}, + } + } + s, err := groups.NewService(ctx, repoFn, 1000) + require.NoError(t, err) + testcases := []struct { + name string + userFunc func() (*iam.User, auth.Account) + expectOutfields []string + }{ + { + name: "grants name and description", + userFunc: iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=name,description"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.NameField, globals.DescriptionField}, + }, + { + name: "grants scope and scopeId", + userFunc: iam.TestUserManagedGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, ldap.TestAuthMethodWithAccountInManagedGroup, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=scope,scope_id"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.ScopeField, globals.ScopeIdField}, + }, + { + name: "grants update_time and create_time", + + userFunc: iam.TestUserManagedGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, oidc.TestAuthMethodWithAccountInManagedGroup, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=updated_time,created_time"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.UpdatedTimeField, globals.CreatedTimeField}, + }, + { + name: "grants id, authorized_actions, version", + userFunc: iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=id,authorized_actions,version"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.IdField, globals.AuthorizedActionsField, globals.VersionField}, + }, + { + name: "composite grants all fields", + userFunc: iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=id"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=scope"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=scope_id"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=name"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=description"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=created_time"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=authorized_actions"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=version"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{ + globals.IdField, + globals.ScopeField, + globals.ScopeIdField, + globals.NameField, + globals.DescriptionField, + globals.CreatedTimeField, + globals.AuthorizedActionsField, + globals.VersionField, + }, + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + user, account := tc.userFunc() + tok, err := atRepo.CreateAuthToken(ctx, user, account.GetPublicId()) + require.NoError(t, err) + fullGrantAuthCtx := cauth.TestAuthContextFromToken(t, conn, wrap, tok, iamRepo) + out, err := s.AddGroupMembers(fullGrantAuthCtx, inputFunc(t)) + require.NoError(t, err) + handlers.TestAssertOutputFields(t, out.Item, tc.expectOutfields) + }) + } + }) + + t.Run("SetGroupMembers", func(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) + kmsCache := kms.TestKms(t, conn, wrap) + atRepo, err := authtoken.NewRepository(ctx, rw, rw, kmsCache) + require.NoError(t, err) + repoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + + // this can be used across test cases because we're only testing for output fields, not the update behaviors + inputFunc := func(t *testing.T) *pbs.SetGroupMembersRequest { + name, _ := uuid.GenerateUUID() + desc, _ := uuid.GenerateUUID() + globalGroupWithMember := iam.TestGroup(t, conn, globals.GlobalPrefix, iam.WithDescription(desc), iam.WithName(name)) + u := iam.TestUser(t, iamRepo, globals.GlobalPrefix) + return &pbs.SetGroupMembersRequest{ + Id: globalGroupWithMember.PublicId, + Version: globalGroupWithMember.Version, + MemberIds: []string{u.PublicId}, + } + } + s, err := groups.NewService(ctx, repoFn, 1000) + require.NoError(t, err) + testcases := []struct { + name string + userFunc func() (*iam.User, auth.Account) + expectOutfields []string + }{ + { + name: "grants name and description", + userFunc: iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=name,description"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.NameField, globals.DescriptionField}, + }, + { + name: "grants scope and scopeId", + userFunc: iam.TestUserManagedGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, ldap.TestAuthMethodWithAccountInManagedGroup, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=scope,scope_id"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.ScopeField, globals.ScopeIdField}, + }, + { + name: "grants update_time and create_time", + + userFunc: iam.TestUserManagedGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, oidc.TestAuthMethodWithAccountInManagedGroup, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=updated_time,created_time"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.UpdatedTimeField, globals.CreatedTimeField}, + }, + { + name: "grants id, authorized_actions, version", + userFunc: iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=id,authorized_actions,version"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.IdField, globals.AuthorizedActionsField, globals.VersionField}, + }, + { + name: "composite grants all fields", + userFunc: iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=id"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=scope"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=scope_id"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=name"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=description"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=created_time"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=authorized_actions"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=version"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{ + globals.IdField, + globals.ScopeField, + globals.ScopeIdField, + globals.NameField, + globals.DescriptionField, + globals.CreatedTimeField, + globals.AuthorizedActionsField, + globals.VersionField, + }, + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + user, account := tc.userFunc() + tok, err := atRepo.CreateAuthToken(ctx, user, account.GetPublicId()) + require.NoError(t, err) + fullGrantAuthCtx := cauth.TestAuthContextFromToken(t, conn, wrap, tok, iamRepo) + out, err := s.SetGroupMembers(fullGrantAuthCtx, inputFunc(t)) + require.NoError(t, err) + handlers.TestAssertOutputFields(t, out.Item, tc.expectOutfields) + }) + } + }) + + t.Run("RemoveGroupMembers", func(t *testing.T) { + ctx := context.Background() + conn, _ := db.TestSetup(t, "postgres") + rw := db.New(conn) + wrap := db.TestWrapper(t) + iamRepo := iam.TestRepo(t, conn, wrap) + kmsCache := kms.TestKms(t, conn, wrap) + atRepo, err := authtoken.NewRepository(ctx, rw, rw, kmsCache) + require.NoError(t, err) + repoFn := func() (*iam.Repository, error) { + return iamRepo, nil + } + + // this can be used across test cases because we're only testing for output fields, not the update behaviors + inputFunc := func(t *testing.T) *pbs.RemoveGroupMembersRequest { + name, _ := uuid.GenerateUUID() + desc, _ := uuid.GenerateUUID() + globalGroupWithMember := iam.TestGroup(t, conn, globals.GlobalPrefix, iam.WithDescription(desc), iam.WithName(name)) + // create 2 users and remove one so the tests can differentiate between the group without members vs. having no access to read members + u1 := iam.TestUser(t, iamRepo, globals.GlobalPrefix) + u2 := iam.TestUser(t, iamRepo, globals.GlobalPrefix) + _ = iam.TestGroupMember(t, conn, globalGroupWithMember.PublicId, u1.PublicId) + _ = iam.TestGroupMember(t, conn, globalGroupWithMember.PublicId, u2.PublicId) + return &pbs.RemoveGroupMembersRequest{ + Id: globalGroupWithMember.PublicId, + Version: globalGroupWithMember.Version, + MemberIds: []string{u2.PublicId}, + } + } + s, err := groups.NewService(ctx, repoFn, 1000) + require.NoError(t, err) + testcases := []struct { + name string + userFunc func() (*iam.User, auth.Account) + expectOutfields []string + }{ + { + name: "grants name and description", + userFunc: iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=name,description"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.NameField, globals.DescriptionField}, + }, + { + name: "grants scope and scopeId", + userFunc: iam.TestUserManagedGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, ldap.TestAuthMethodWithAccountInManagedGroup, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=scope,scope_id"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.ScopeField, globals.ScopeIdField}, + }, + { + name: "grants update_time and create_time", + + userFunc: iam.TestUserManagedGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, oidc.TestAuthMethodWithAccountInManagedGroup, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=updated_time,created_time"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.UpdatedTimeField, globals.CreatedTimeField}, + }, + { + name: "grants id, authorized_actions, version", + userFunc: iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=id,authorized_actions,version"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{globals.IdField, globals.AuthorizedActionsField, globals.VersionField}, + }, + { + name: "composite grants all fields", + userFunc: iam.TestUserGroupGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{ + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=id"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=scope"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=scope_id"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=name"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=description"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=created_time"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=authorized_actions"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + { + RoleScopeId: globals.GlobalPrefix, + Grants: []string{"ids=*;type=group;actions=*;output_fields=version"}, + GrantScopes: []string{globals.GrantScopeThis}, + }, + }), + expectOutfields: []string{ + globals.IdField, + globals.ScopeField, + globals.ScopeIdField, + globals.NameField, + globals.DescriptionField, + globals.CreatedTimeField, + globals.AuthorizedActionsField, + globals.VersionField, + }, + }, + } + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + user, account := tc.userFunc() + tok, err := atRepo.CreateAuthToken(ctx, user, account.GetPublicId()) + require.NoError(t, err) + fullGrantAuthCtx := cauth.TestAuthContextFromToken(t, conn, wrap, tok, iamRepo) + out, err := s.RemoveGroupMembers(fullGrantAuthCtx, inputFunc(t)) + require.NoError(t, err) + handlers.TestAssertOutputFields(t, out.Item, tc.expectOutfields) + }) + } + }) +} + +func userIDs(users []*iam.User) []string { + result := make([]string, len(users)) + for i, u := range users { + result[i] = u.PublicId + } + return result } diff --git a/internal/daemon/controller/handlers/host_catalogs/grants_test.go b/internal/daemon/controller/handlers/host_catalogs/grants_test.go index 9576fd010e..e8df94081b 100644 --- a/internal/daemon/controller/handlers/host_catalogs/grants_test.go +++ b/internal/daemon/controller/handlers/host_catalogs/grants_test.go @@ -88,7 +88,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: globals.GlobalPrefix, + RoleScopeId: globals.GlobalPrefix, GrantStrings: []string{"ids=*;type=host-catalog;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeDescendants}, }, @@ -104,7 +104,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: org.PublicId, + RoleScopeId: org.PublicId, GrantStrings: []string{"ids=*;type=host-catalog;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, }, @@ -120,7 +120,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: proj.PublicId, + RoleScopeId: proj.PublicId, GrantStrings: []string{"ids=*;type=host-catalog;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis}, }, diff --git a/internal/daemon/controller/handlers/host_sets/grants_test.go b/internal/daemon/controller/handlers/host_sets/grants_test.go index 4fa763221f..4920ab219c 100644 --- a/internal/daemon/controller/handlers/host_sets/grants_test.go +++ b/internal/daemon/controller/handlers/host_sets/grants_test.go @@ -79,7 +79,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: globals.GlobalPrefix, + RoleScopeId: globals.GlobalPrefix, GrantStrings: []string{"ids=*;type=host-set;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeDescendants}, }, @@ -94,7 +94,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: org.PublicId, + RoleScopeId: org.PublicId, GrantStrings: []string{"ids=*;type=host-set;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, }, @@ -109,7 +109,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: proj.PublicId, + RoleScopeId: proj.PublicId, GrantStrings: []string{"ids=*;type=host-set;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis}, }, diff --git a/internal/daemon/controller/handlers/hosts/grants_test.go b/internal/daemon/controller/handlers/hosts/grants_test.go index 6715878bfb..c3532ebebb 100644 --- a/internal/daemon/controller/handlers/hosts/grants_test.go +++ b/internal/daemon/controller/handlers/hosts/grants_test.go @@ -81,7 +81,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: globals.GlobalPrefix, + RoleScopeId: globals.GlobalPrefix, GrantStrings: []string{"ids=*;type=host;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeDescendants}, }, @@ -96,7 +96,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: org.PublicId, + RoleScopeId: org.PublicId, GrantStrings: []string{"ids=*;type=host;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, }, @@ -111,7 +111,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: proj.PublicId, + RoleScopeId: proj.PublicId, GrantStrings: []string{"ids=*;type=host;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis}, }, diff --git a/internal/daemon/controller/handlers/managed_groups/grants_test.go b/internal/daemon/controller/handlers/managed_groups/grants_test.go index f73ba91c9f..1196c5e7c4 100644 --- a/internal/daemon/controller/handlers/managed_groups/grants_test.go +++ b/internal/daemon/controller/handlers/managed_groups/grants_test.go @@ -78,7 +78,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: globals.GlobalPrefix, + RoleScopeId: globals.GlobalPrefix, GrantStrings: []string{"ids=*;type=managed-group;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeDescendants}, }, @@ -93,7 +93,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: org.PublicId, + RoleScopeId: org.PublicId, GrantStrings: []string{"ids=*;type=managed-group;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, }, diff --git a/internal/daemon/controller/handlers/roles/grants_test.go b/internal/daemon/controller/handlers/roles/grants_test.go index e55c62121f..b7f61ee5a6 100644 --- a/internal/daemon/controller/handlers/roles/grants_test.go +++ b/internal/daemon/controller/handlers/roles/grants_test.go @@ -110,7 +110,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: globals.GlobalPrefix, + RoleScopeId: globals.GlobalPrefix, GrantStrings: []string{"ids=*;type=role;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, }, @@ -130,7 +130,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: org2.PublicId, + RoleScopeId: org2.PublicId, GrantStrings: []string{"ids=*;type=role;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, }, diff --git a/internal/daemon/controller/handlers/scopes/grants_test.go b/internal/daemon/controller/handlers/scopes/grants_test.go index b3f99944b4..15e9ae2ef7 100644 --- a/internal/daemon/controller/handlers/scopes/grants_test.go +++ b/internal/daemon/controller/handlers/scopes/grants_test.go @@ -60,7 +60,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: globals.GlobalPrefix, + RoleScopeId: globals.GlobalPrefix, GrantStrings: []string{"ids=*;type=scope;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, }, @@ -79,7 +79,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: org1.PublicId, + RoleScopeId: org1.PublicId, GrantStrings: []string{"ids=*;type=scope;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, }, diff --git a/internal/daemon/controller/handlers/targets/tcp/grants_test.go b/internal/daemon/controller/handlers/targets/tcp/grants_test.go index cb91035926..509aff1a56 100644 --- a/internal/daemon/controller/handlers/targets/tcp/grants_test.go +++ b/internal/daemon/controller/handlers/targets/tcp/grants_test.go @@ -60,7 +60,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: org1.GetPublicId(), + RoleScopeId: org1.GetPublicId(), GrantStrings: []string{"ids=*;type=target;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, }, @@ -76,7 +76,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: proj2.GetPublicId(), + RoleScopeId: proj2.GetPublicId(), GrantStrings: []string{"ids=*;type=target;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis}, }, diff --git a/internal/daemon/controller/handlers/testing.go b/internal/daemon/controller/handlers/testing.go new file mode 100644 index 0000000000..98897952c5 --- /dev/null +++ b/internal/daemon/controller/handlers/testing.go @@ -0,0 +1,29 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package handlers + +import ( + "testing" + + "github.com/stretchr/testify/require" + "golang.org/x/exp/slices" + "google.golang.org/protobuf/proto" +) + +// TestAssertOutputFields asserts that the output fields of a group match the expected fields +// fields that is nil or empty in the result will throw an error if they are listed in expectedFields +// e.g. members when group does not contain any members +func TestAssertOutputFields(t *testing.T, p proto.Message, expectFields []string) { + msg := p.ProtoReflect() + descriptor := msg.Descriptor() + for i := 0; i < descriptor.Fields().Len(); i++ { + fd := descriptor.Fields().Get(i) + fieldName := string(fd.Name()) + if !slices.Contains(expectFields, fieldName) { + require.Falsef(t, msg.Has(fd), "expect field '%s' to be empty but got %+v", fd.Name(), msg.Get(fd).Interface()) + continue + } + require.Truef(t, msg.Has(fd), "expect field '%s' NOT be empty but got %+v", fd.Name(), msg.Get(fd).Interface()) + } +} diff --git a/internal/daemon/controller/handlers/users/grants_test.go b/internal/daemon/controller/handlers/users/grants_test.go index a4bc80c486..7f04b8105a 100644 --- a/internal/daemon/controller/handlers/users/grants_test.go +++ b/internal/daemon/controller/handlers/users/grants_test.go @@ -78,7 +78,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: globals.GlobalPrefix, + RoleScopeId: globals.GlobalPrefix, GrantStrings: []string{"ids=*;type=user;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, }, @@ -104,7 +104,7 @@ func TestGrants_ReadActions(t *testing.T) { includeTestUsers: false, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: org2.PublicId, + RoleScopeId: org2.PublicId, GrantStrings: []string{"ids=*;type=user;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren}, }, @@ -124,7 +124,7 @@ func TestGrants_ReadActions(t *testing.T) { includeTestUsers: false, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: globals.GlobalPrefix, + RoleScopeId: globals.GlobalPrefix, GrantStrings: []string{"ids=*;type=user;actions=list,read"}, GrantScopes: []string{globals.GrantScopeChildren}, }, @@ -144,7 +144,7 @@ func TestGrants_ReadActions(t *testing.T) { includeTestUsers: false, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: globals.GlobalPrefix, + RoleScopeId: globals.GlobalPrefix, GrantStrings: []string{"ids=*;type=user;actions=list,read"}, GrantScopes: []string{globals.GrantScopeChildren}, }, @@ -165,7 +165,7 @@ func TestGrants_ReadActions(t *testing.T) { includeTestUsers: false, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: org1.PublicId, + RoleScopeId: org1.PublicId, GrantStrings: []string{"ids=*;type=user;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis}, }, @@ -184,12 +184,12 @@ func TestGrants_ReadActions(t *testing.T) { includeTestUsers: false, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: org1.PublicId, + RoleScopeId: org1.PublicId, GrantStrings: []string{"ids=*;type=user;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis}, }, { - RoleScopeID: org2.PublicId, + RoleScopeId: org2.PublicId, GrantStrings: []string{"ids=*;type=user;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis}, }, @@ -210,7 +210,7 @@ func TestGrants_ReadActions(t *testing.T) { includeTestUsers: false, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: globals.GlobalPrefix, + RoleScopeId: globals.GlobalPrefix, GrantStrings: []string{"ids=*;type=user;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis}, }, @@ -227,7 +227,7 @@ func TestGrants_ReadActions(t *testing.T) { includeTestUsers: true, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: globals.GlobalPrefix, + RoleScopeId: globals.GlobalPrefix, GrantStrings: []string{"ids=*;type=user;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis}, }, @@ -248,7 +248,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: globals.GlobalPrefix, + RoleScopeId: globals.GlobalPrefix, GrantStrings: []string{"ids=*;type=user;actions=list,read"}, GrantScopes: []string{globals.GrantScopeChildren}, }, @@ -264,7 +264,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: org2.PublicId, + RoleScopeId: org2.PublicId, GrantStrings: []string{"ids=*;type=user;actions=list,read"}, GrantScopes: []string{globals.GrantScopeChildren}, }, diff --git a/internal/daemon/controller/handlers/workers/grants_test.go b/internal/daemon/controller/handlers/workers/grants_test.go index dc1aef0d28..6ec676c179 100644 --- a/internal/daemon/controller/handlers/workers/grants_test.go +++ b/internal/daemon/controller/handlers/workers/grants_test.go @@ -81,7 +81,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: globals.GlobalPrefix, + RoleScopeId: globals.GlobalPrefix, GrantStrings: []string{"ids=*;type=worker;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis}, }, @@ -97,7 +97,7 @@ func TestGrants_ReadActions(t *testing.T) { }, rolesToCreate: []authtoken.TestRoleGrantsForToken{ { - RoleScopeID: globals.GlobalPrefix, + RoleScopeId: globals.GlobalPrefix, GrantStrings: []string{"ids=*;type=group;actions=list,read"}, GrantScopes: []string{globals.GrantScopeThis}, }, diff --git a/internal/iam/testing.go b/internal/iam/testing.go index 970a920acb..3d8effea70 100644 --- a/internal/iam/testing.go +++ b/internal/iam/testing.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/hashicorp/boundary/globals" + "github.com/hashicorp/boundary/internal/auth" "github.com/hashicorp/boundary/internal/auth/store" "github.com/hashicorp/boundary/internal/db" dbassert "github.com/hashicorp/boundary/internal/db/assert" @@ -223,36 +224,6 @@ func TestRole(t testing.TB, conn *db.DB, scopeId string, opt ...Option) *Role { return role } -// TestRoleWithGrants creates a role suitable for testing along with grants -// Functional options for GrantScopeIDs aren't used to express that -// this function does not provide any default grant scope unlike TestRole -func TestRoleWithGrants(t testing.TB, conn *db.DB, scopeId string, grantScopeIDs []string, grants []string) *Role { - t.Helper() - - ctx := context.Background() - require := require.New(t) - rw := db.New(conn) - - role, err := NewRole(ctx, scopeId) - require.NoError(err) - id, err := newRoleId(ctx) - require.NoError(err) - role.PublicId = id - require.NoError(rw.Create(ctx, role)) - require.NotEmpty(role.PublicId) - - for _, gsi := range grantScopeIDs { - gs, err := NewRoleGrantScope(ctx, id, gsi) - require.NoError(err) - require.NoError(rw.Create(ctx, gs)) - role.GrantScopes = append(role.GrantScopes, gs) - } - for _, g := range grants { - _ = TestRoleGrant(t, conn, role.PublicId, g) - } - return role -} - func TestRoleGrant(t testing.TB, conn *db.DB, roleId, grant string, opt ...Option) *RoleGrant { t.Helper() require := require.New(t) @@ -345,6 +316,148 @@ func TestManagedGroupRole(t testing.TB, conn *db.DB, roleId, managedGrpId string return r } +// TestRoleWithGrants creates a role suitable for testing along with grants +// Functional options for GrantScopes aren't used to express that +// this function does not provide any default grant scope unlike TestRole +func TestRoleWithGrants(t testing.TB, conn *db.DB, scopeId string, grantScopeIds []string, grants []string) *Role { + t.Helper() + + ctx := context.Background() + require := require.New(t) + rw := db.New(conn) + + role, err := NewRole(ctx, scopeId) + require.NoError(err) + id, err := newRoleId(ctx) + require.NoError(err) + role.PublicId = id + require.NoError(rw.Create(ctx, role)) + require.NotEmpty(role.PublicId) + + for _, gsi := range grantScopeIds { + gs, err := NewRoleGrantScope(ctx, id, gsi) + require.NoError(err) + require.NoError(rw.Create(ctx, gs)) + role.GrantScopes = append(role.GrantScopes, gs) + } + for _, g := range grants { + _ = TestRoleGrant(t, conn, role.PublicId, g) + } + return role +} + +type TestRoleGrantsRequest struct { + RoleScopeId string + GrantScopes []string + Grants []string +} + +// TestUserManagedGroupGrantsFunc returns a function that creates a user which has been given +// the request grants through managed group. +// Note: This method is not responsible for associating the user to the managed group. That action needs to be done +// by the caller +// This function returns iam.User and the AccountID from the account setup func +func TestUserManagedGroupGrantsFunc( + t *testing.T, + conn *db.DB, + kmsCache *kms.Kms, + scopeId string, + managedGroupAccountSetupFunc auth.TestAuthMethodWithAccountInManagedGroup, + testRoleGrants []TestRoleGrantsRequest, +) func() (*User, auth.Account) { + return func() (*User, auth.Account) { + t.Helper() + ctx := context.Background() + rw := db.New(conn) + repo, err := NewRepository(ctx, rw, rw, kmsCache) + require.NoError(t, err) + _, account, mg := managedGroupAccountSetupFunc(t, conn, kmsCache, scopeId) + user := TestUser(t, repo, scopeId, WithAccountIds(account.GetPublicId())) + for _, trg := range testRoleGrants { + role := TestRoleWithGrants(t, conn, trg.RoleScopeId, trg.GrantScopes, trg.Grants) + _ = TestManagedGroupRole(t, conn, role.PublicId, mg.GetPublicId()) + } + user, acctIDs, err := repo.LookupUser(ctx, user.PublicId) + require.NoError(t, err) + require.Len(t, acctIDs, 1) + return user, account + } +} + +// TestUserDirectGrantsFunc returns a function that creates and returns user which has been given +// the request grants via direct association. +// This function returns iam.User and the AccountID from the account setup func +func TestUserDirectGrantsFunc( + t *testing.T, + conn *db.DB, + kmsCache *kms.Kms, + scopeId string, + setupFunc auth.TestAuthMethodWithAccountFunc, + testRoleGrants []TestRoleGrantsRequest, +) func() (*User, auth.Account) { + return func() (*User, auth.Account) { + t.Helper() + _, account := setupFunc(t, conn) + ctx := context.Background() + rw := db.New(conn) + repo, err := NewRepository(ctx, rw, rw, kmsCache) + require.NoError(t, err) + user := TestUser(t, repo, scopeId, WithAccountIds(account.GetPublicId())) + require.NoError(t, err) + for _, trg := range testRoleGrants { + role := TestRoleWithGrants(t, conn, trg.RoleScopeId, trg.GrantScopes, trg.Grants) + _ = TestUserRole(t, conn, role.PublicId, user.PublicId) + } + user, acctIDs, err := repo.LookupUser(ctx, user.PublicId) + require.NoError(t, err) + require.Len(t, acctIDs, 1) + return user, account + } +} + +// TestUserGroupGrantsFunc returns a function that creates a user which has been given +// the request grants by being a part of a group. +// Group is created as a part of this method +// This function returns iam.User and the AccountID from the account setup func +func TestUserGroupGrantsFunc( + t *testing.T, + conn *db.DB, + kmsCache *kms.Kms, + scopeId string, + setupFunc auth.TestAuthMethodWithAccountFunc, + testRoleGrants []TestRoleGrantsRequest, +) func() (*User, auth.Account) { + return func() (*User, auth.Account) { + t.Helper() + _, account := setupFunc(t, conn) + ctx := context.Background() + rw := db.New(conn) + repo, err := NewRepository(ctx, rw, rw, kmsCache) + require.NoError(t, err) + role, err := NewRole(ctx, scopeId) + require.NoError(t, err) + id, err := newRoleId(ctx) + require.NoError(t, err) + role.PublicId = id + require.NoError(t, rw.Create(ctx, role)) + require.NotEmpty(t, role.PublicId) + require.NoError(t, err) + group := TestGroup(t, conn, scopeId) + require.NoError(t, err) + user := TestUser(t, repo, scopeId, WithAccountIds(account.GetPublicId())) + for _, trg := range testRoleGrants { + role := TestRoleWithGrants(t, conn, trg.RoleScopeId, trg.GrantScopes, trg.Grants) + _ = TestGroupRole(t, conn, role.PublicId, group.PublicId) + } + _, err = repo.AddGroupMembers(ctx, group.PublicId, group.Version, []string{user.PublicId}) + require.NoError(t, err) + user, acctIDs, err := repo.LookupUser(ctx, user.PublicId) + require.NoError(t, err) + require.Len(t, acctIDs, 1) + return user, account + } +} + // testAccount is a temporary test function. TODO - replace with an auth // subsystem testAccount function. If userId is zero value, then an auth // account will be created with a null IamUserId