You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/daemon/controller/handlers/authmethods/grants_test.go

1373 lines
60 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package authmethods_test
import (
"context"
"fmt"
"os"
"slices"
"sync"
"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"
controllerauth "github.com/hashicorp/boundary/internal/daemon/controller/auth"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/authmethods"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/event"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/types/resource"
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authmethods"
"github.com/hashicorp/go-hclog"
"github.com/stretchr/testify/require"
"google.golang.org/genproto/protobuf/field_mask"
"google.golang.org/protobuf/types/known/wrapperspb"
)
// expectedOutput consolidates common output fields for the test cases
type expectedOutput struct {
wantErr error
wantOutfields []string
}
// TestGrants_ReadActions tests read actions to assert that grants are being applied properly
func TestGrants_ReadActions(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
wrap := db.TestWrapper(t)
rw := db.New(conn)
kmsCache := kms.TestKms(t, conn, wrap)
iamRepo := iam.TestRepo(t, conn, wrap)
atRepo, err := authtoken.NewRepository(ctx, rw, rw, kmsCache)
require.NoError(t, err)
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
oidcRepoFn := func() (*oidc.Repository, error) {
return oidc.NewRepository(ctx, rw, rw, kmsCache)
}
ldapRepoFn := func() (*ldap.Repository, error) {
return ldap.NewRepository(ctx, rw, rw, kmsCache)
}
pwRepoFn := func() (*password.Repository, error) {
return password.NewRepository(ctx, rw, rw, kmsCache)
}
atRepoFn := func() (*authtoken.Repository, error) {
return atRepo, nil
}
authMethodRepoFn := func() (*auth.AuthMethodRepository, error) {
return auth.NewAuthMethodRepository(ctx, rw, rw, kmsCache)
}
s, err := authmethods.NewService(ctx,
kmsCache,
pwRepoFn,
oidcRepoFn,
iamRepoFn,
atRepoFn,
ldapRepoFn,
authMethodRepoFn,
1000)
require.NoError(t, err)
org1, p1 := iam.TestScopes(t, iamRepo)
org2, _ := iam.TestScopes(t, iamRepo)
// Need to create a wrapper for each scope (global, org1, org2) to test grants against OIDC/LDAP at that scope
databaseWrapper, err := kmsCache.GetWrapper(context.Background(), globals.GlobalPrefix, kms.KeyPurposeDatabase)
require.NoError(t, err)
databaseWrapperOrg1, err := kmsCache.GetWrapper(context.Background(), org1.GetPublicId(), kms.KeyPurposeDatabase)
require.NoError(t, err)
databaseWrapperOrg2, err := kmsCache.GetWrapper(context.Background(), org2.GetPublicId(), kms.KeyPurposeDatabase)
require.NoError(t, err)
// Create OIDC/LDAP/Password auth methods for each scope
oidcGlobal := oidc.TestAuthMethod(t, conn, databaseWrapper, globals.GlobalPrefix, oidc.InactiveState, "client-id", "secret", oidc.WithName("oidc-global"), oidc.WithDescription("test oidc desc"))
oidcOrg1 := oidc.TestAuthMethod(t, conn, databaseWrapperOrg1, org1.GetPublicId(), oidc.InactiveState, "client-id", "secret", oidc.WithName("oidc-org-1"), oidc.WithDescription("test oidc desc"))
oidcOrg2 := oidc.TestAuthMethod(t, conn, databaseWrapperOrg2, org2.GetPublicId(), oidc.InactiveState, "client-id", "secret", oidc.WithName("oidc-org-2"), oidc.WithDescription("test oidc desc"))
ldapGlobal := ldap.TestAuthMethod(t, conn, databaseWrapper, globals.GlobalPrefix, []string{"ldaps://alice.com"}, ldap.WithName(ctx, "ldap-global"), ldap.WithDescription(ctx, "test ldap desc"))
ldapOrg1 := ldap.TestAuthMethod(t, conn, databaseWrapperOrg1, org1.GetPublicId(), []string{"ldaps://alice.com"}, ldap.WithName(ctx, "ldap-org-1"), ldap.WithDescription(ctx, "test ldap desc"))
ldapOrg2 := ldap.TestAuthMethod(t, conn, databaseWrapperOrg2, org2.GetPublicId(), []string{"ldaps://alice.com"}, ldap.WithName(ctx, "ldap-org-2"), ldap.WithDescription(ctx, "test ldap desc"))
pwGlobal := password.TestAuthMethod(t, conn, globals.GlobalPrefix, password.WithName("pw-global"), password.WithDescription("test pw desc"))
pwOrg1 := password.TestAuthMethod(t, conn, org1.GetPublicId(), password.WithName("pw-org-1"), password.WithDescription("test pw desc"))
pwOrg2 := password.TestAuthMethod(t, conn, org2.GetPublicId(), password.WithName("pw-org-2"), password.WithDescription("test pw desc"))
// ignoreList consists of auth method IDs to omit from the result set
// this is to handle auth methods that are created during the auth token
// creation
ignoreList := []string{}
t.Run("List", func(t *testing.T) {
testcases := []struct {
name string
input *pbs.ListAuthMethodsRequest
userFunc func() (*iam.User, auth.Account)
wantErr error
wantIDs []string
expectOutfields map[string][]string
}{
{
name: "global role grant this only returns global auth methods",
input: &pbs.ListAuthMethodsRequest{ScopeId: globals.GlobalPrefix},
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: globals.GlobalPrefix,
Grants: []string{"ids=*;type=*;actions=*;output_fields=id,scope_id,name,description,type,created_time,updated_time,version,type"},
GrantScopes: []string{globals.GrantScopeThis},
},
}),
wantIDs: []string{
oidcGlobal.PublicId,
ldapGlobal.PublicId,
pwGlobal.PublicId,
},
expectOutfields: map[string][]string{
oidcGlobal.PublicId: {globals.IdField, globals.ScopeIdField, globals.NameField, globals.DescriptionField, globals.TypeField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField, globals.TypeField},
ldapGlobal.PublicId: {globals.IdField, globals.ScopeIdField, globals.NameField, globals.DescriptionField, globals.TypeField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField, globals.TypeField},
pwGlobal.PublicId: {globals.IdField, globals.ScopeIdField, globals.NameField, globals.DescriptionField, globals.TypeField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField, globals.TypeField},
},
},
{
name: "global role grant this recursively returns all auth methods",
input: &pbs.ListAuthMethodsRequest{
ScopeId: globals.GlobalPrefix,
Recursive: true,
},
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: globals.GlobalPrefix,
Grants: []string{"ids=*;type=auth-method;actions=list,no-op;output_fields=id,scope,authorized_actions"},
GrantScopes: []string{globals.GrantScopeThis},
},
}),
wantIDs: []string{
oidcGlobal.PublicId,
oidcOrg1.PublicId,
oidcOrg2.PublicId,
ldapGlobal.PublicId,
ldapOrg1.PublicId,
ldapOrg2.PublicId,
pwGlobal.PublicId,
pwOrg1.PublicId,
pwOrg2.PublicId,
},
expectOutfields: map[string][]string{
oidcGlobal.PublicId: {globals.IdField, globals.ScopeField, globals.AuthorizedActionsField},
ldapGlobal.PublicId: {globals.IdField, globals.ScopeField, globals.AuthorizedActionsField},
pwGlobal.PublicId: {globals.IdField, globals.ScopeField, globals.AuthorizedActionsField},
},
},
{
name: "global role grant this and children returns global auth methods",
input: &pbs.ListAuthMethodsRequest{
ScopeId: globals.GlobalPrefix,
},
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: globals.GlobalPrefix,
Grants: []string{"ids=*;type=auth-method;actions=list,no-op;output_fields=id,authorized_actions"},
GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren},
},
}),
wantIDs: []string{
oidcGlobal.PublicId,
ldapGlobal.PublicId,
pwGlobal.PublicId,
},
expectOutfields: map[string][]string{
oidcGlobal.PublicId: {globals.IdField, globals.AuthorizedActionsField},
ldapGlobal.PublicId: {globals.IdField, globals.AuthorizedActionsField},
pwGlobal.PublicId: {globals.IdField, globals.AuthorizedActionsField},
},
},
{
name: "global role grant this and children recursive returns all auth methods",
input: &pbs.ListAuthMethodsRequest{
ScopeId: globals.GlobalPrefix,
Recursive: true,
},
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: globals.GlobalPrefix,
Grants: []string{"ids=*;type=auth-method;actions=list,no-op;output_fields=id,name"},
GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren},
},
}),
wantIDs: []string{
oidcGlobal.PublicId,
oidcOrg1.PublicId,
oidcOrg2.PublicId,
ldapGlobal.PublicId,
ldapOrg1.PublicId,
ldapOrg2.PublicId,
pwGlobal.PublicId,
pwOrg1.PublicId,
pwOrg2.PublicId,
},
expectOutfields: map[string][]string{
oidcGlobal.PublicId: {globals.IdField, globals.NameField},
oidcOrg1.PublicId: {globals.IdField, globals.NameField},
oidcOrg2.PublicId: {globals.IdField, globals.NameField},
ldapGlobal.PublicId: {globals.IdField, globals.NameField},
ldapOrg1.PublicId: {globals.IdField, globals.NameField},
ldapOrg2.PublicId: {globals.IdField, globals.NameField},
pwGlobal.PublicId: {globals.IdField, globals.NameField},
pwOrg1.PublicId: {globals.IdField, globals.NameField},
pwOrg2.PublicId: {globals.IdField, globals.NameField},
},
},
{
name: "no grants recursive return children org",
input: &pbs.ListAuthMethodsRequest{
ScopeId: globals.GlobalPrefix,
Recursive: true,
},
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: globals.GlobalPrefix,
Grants: []string{},
GrantScopes: []string{},
},
}),
wantErr: nil,
// auth methods in `global` are filtered out because the user does not have grants to read
// them in the global scope. Auth methods in org 1 and org 2 show up - my guess is because
// the u_anon grants allow auth methods to be read on any org.
wantIDs: []string{
// oidcGlobal.PublicId,
oidcOrg1.PublicId,
oidcOrg2.PublicId,
// ldapGlobal.PublicId,
ldapOrg1.PublicId,
ldapOrg2.PublicId,
// pwGlobal.PublicId,
pwOrg1.PublicId,
pwOrg2.PublicId,
},
expectOutfields: map[string][]string{},
},
{
name: "org role grant this and children returns auth methods in org1",
input: &pbs.ListAuthMethodsRequest{
ScopeId: org1.PublicId,
},
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: org1.PublicId,
Grants: []string{"ids=*;type=auth-method;actions=list,no-op;output_fields=id,scope_id,name,description,type,created_time,updated_time,version,type"},
GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren},
},
}),
wantIDs: []string{
oidcOrg1.PublicId,
ldapOrg1.PublicId,
pwOrg1.PublicId,
},
expectOutfields: map[string][]string{
oidcOrg1.PublicId: {globals.IdField, globals.ScopeIdField, globals.NameField, globals.DescriptionField, globals.TypeField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField, globals.TypeField},
ldapOrg1.PublicId: {globals.IdField, globals.ScopeIdField, globals.NameField, globals.DescriptionField, globals.TypeField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField, globals.TypeField},
pwOrg1.PublicId: {globals.IdField, globals.ScopeIdField, globals.NameField, globals.DescriptionField, globals.TypeField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField, globals.TypeField},
},
},
{
name: "project role can't list auth methods (403)",
input: &pbs.ListAuthMethodsRequest{
ScopeId: globals.GlobalPrefix,
},
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: p1.PublicId,
Grants: []string{"ids=*;type=auth-method;actions=list,no-op;output_fields=id,scope_id,name"},
GrantScopes: []string{globals.GrantScopeThis},
},
}),
wantErr: handlers.ForbiddenError(),
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
// Call function to create direct/indirect relationship
user, account := tc.userFunc()
// Create auth token for the user
tok, err := atRepo.CreateAuthToken(ctx, user, account.GetPublicId())
require.NoError(t, err)
// Create auth context from the auth token
fullGrantAuthCtx := controllerauth.TestAuthContextFromToken(t, conn, wrap, tok, iamRepo)
// auth method created during token generation will not be taken into considerations during this test
// adding to the ignoreList so it can be ignored later
ignoreList = append(ignoreList, tok.GetAuthMethodId())
got, finalErr := s.ListAuthMethods(fullGrantAuthCtx, tc.input)
if tc.wantErr != nil {
require.ErrorIs(t, finalErr, tc.wantErr)
return
}
require.NoError(t, finalErr)
var gotIDs []string
for _, g := range got.Items {
// do not include IDs in the ignore list in the result sets
if slices.Contains(ignoreList, g.Id) {
continue
}
gotIDs = append(gotIDs, g.GetId())
// check if the output fields are as expected
if tc.expectOutfields[g.Id] != nil {
handlers.TestAssertOutputFields(t, g, tc.expectOutfields[g.Id])
}
}
require.ElementsMatch(t, tc.wantIDs, gotIDs)
})
}
})
t.Run("Get", func(t *testing.T) {
testcases := []struct {
name string
userFunc func() (*iam.User, auth.Account)
canGetAuthMethod map[string]expectedOutput
}{
{
name: "global role grant this can only get global auth methods",
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: globals.GlobalPrefix,
Grants: []string{"ids=*;type=*;actions=*;output_fields=id,scope_id,name,description,type,created_time,updated_time,version,type"},
GrantScopes: []string{globals.GrantScopeThis},
},
}),
canGetAuthMethod: map[string]expectedOutput{
pwGlobal.PublicId: {wantOutfields: []string{globals.IdField, globals.ScopeIdField, globals.NameField, globals.DescriptionField, globals.TypeField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField, globals.TypeField}},
pwOrg1.PublicId: {wantErr: handlers.ForbiddenError()},
pwOrg2.PublicId: {wantErr: handlers.ForbiddenError()},
oidcGlobal.PublicId: {wantOutfields: []string{globals.IdField, globals.ScopeIdField, globals.NameField, globals.DescriptionField, globals.TypeField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField, globals.TypeField}},
oidcOrg1.PublicId: {wantErr: handlers.ForbiddenError()},
oidcOrg2.PublicId: {wantErr: handlers.ForbiddenError()},
ldapGlobal.PublicId: {wantOutfields: []string{globals.IdField, globals.ScopeIdField, globals.NameField, globals.DescriptionField, globals.TypeField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField, globals.TypeField}},
ldapOrg1.PublicId: {wantErr: handlers.ForbiddenError()},
ldapOrg2.PublicId: {wantErr: handlers.ForbiddenError()},
},
},
{
name: "global role grant children can only get org auth methods",
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: globals.GlobalPrefix,
Grants: []string{"ids=*;type=*;actions=*;output_fields=id,name,description"},
GrantScopes: []string{globals.GrantScopeChildren},
},
}),
canGetAuthMethod: map[string]expectedOutput{
pwGlobal.PublicId: {wantErr: handlers.ForbiddenError()},
pwOrg1.PublicId: {wantOutfields: []string{globals.IdField, globals.NameField, globals.DescriptionField}},
pwOrg2.PublicId: {wantOutfields: []string{globals.IdField, globals.NameField, globals.DescriptionField}},
oidcGlobal.PublicId: {wantErr: handlers.ForbiddenError()},
oidcOrg1.PublicId: {wantOutfields: []string{globals.IdField, globals.NameField, globals.DescriptionField}},
oidcOrg2.PublicId: {wantOutfields: []string{globals.IdField, globals.NameField, globals.DescriptionField}},
ldapGlobal.PublicId: {wantErr: handlers.ForbiddenError()},
ldapOrg1.PublicId: {wantOutfields: []string{globals.IdField, globals.NameField, globals.DescriptionField}},
ldapOrg2.PublicId: {wantOutfields: []string{globals.IdField, globals.NameField, globals.DescriptionField}},
},
},
{
name: "global role grant descendants can only get org auth methods",
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: globals.GlobalPrefix,
Grants: []string{"ids=*;type=*;actions=*;output_fields=id,created_time,updated_time,version"},
GrantScopes: []string{globals.GrantScopeDescendants},
},
}),
canGetAuthMethod: map[string]expectedOutput{
pwGlobal.PublicId: {wantErr: handlers.ForbiddenError()},
pwOrg1.PublicId: {wantOutfields: []string{globals.IdField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField}},
pwOrg2.PublicId: {wantOutfields: []string{globals.IdField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField}},
oidcGlobal.PublicId: {wantErr: handlers.ForbiddenError()},
oidcOrg1.PublicId: {wantOutfields: []string{globals.IdField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField}},
oidcOrg2.PublicId: {wantOutfields: []string{globals.IdField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField}},
ldapGlobal.PublicId: {wantErr: handlers.ForbiddenError()},
ldapOrg1.PublicId: {wantOutfields: []string{globals.IdField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField}},
ldapOrg2.PublicId: {wantOutfields: []string{globals.IdField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField}},
},
},
{
name: "global role grant this and children with all permissions returns all auth methods",
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: globals.GlobalPrefix,
Grants: []string{"ids=*;type=*;actions=*;output_fields=id,created_time,updated_time,version"},
GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren},
},
}),
canGetAuthMethod: map[string]expectedOutput{
pwGlobal.PublicId: {wantOutfields: []string{globals.IdField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField}},
pwOrg1.PublicId: {wantOutfields: []string{globals.IdField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField}},
pwOrg2.PublicId: {wantOutfields: []string{globals.IdField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField}},
oidcGlobal.PublicId: {wantOutfields: []string{globals.IdField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField}},
oidcOrg1.PublicId: {wantOutfields: []string{globals.IdField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField}},
oidcOrg2.PublicId: {wantOutfields: []string{globals.IdField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField}},
ldapGlobal.PublicId: {wantOutfields: []string{globals.IdField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField}},
ldapOrg1.PublicId: {wantOutfields: []string{globals.IdField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField}},
ldapOrg2.PublicId: {wantOutfields: []string{globals.IdField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField}},
},
},
{
name: "org1 role grant this scope with all permissions",
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: org1.PublicId,
Grants: []string{"ids=*;type=*;actions=*;output_fields=id,scope_id,type"},
GrantScopes: []string{globals.GrantScopeThis},
},
}),
canGetAuthMethod: map[string]expectedOutput{
pwGlobal.PublicId: {wantErr: handlers.ForbiddenError()},
pwOrg1.PublicId: {wantOutfields: []string{globals.IdField, globals.ScopeIdField, globals.TypeField}},
pwOrg2.PublicId: {wantErr: handlers.ForbiddenError()},
oidcGlobal.PublicId: {wantErr: handlers.ForbiddenError()},
oidcOrg1.PublicId: {wantOutfields: []string{globals.IdField, globals.ScopeIdField, globals.TypeField}},
oidcOrg2.PublicId: {wantErr: handlers.ForbiddenError()},
ldapGlobal.PublicId: {wantErr: handlers.ForbiddenError()},
ldapOrg1.PublicId: {wantOutfields: []string{globals.IdField, globals.ScopeIdField, globals.TypeField}},
ldapOrg2.PublicId: {wantErr: handlers.ForbiddenError()},
},
},
{
name: "no grants returns no auth methods",
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, nil),
canGetAuthMethod: map[string]expectedOutput{
pwGlobal.PublicId: {wantErr: handlers.ForbiddenError()},
pwOrg1.PublicId: {wantErr: handlers.ForbiddenError()},
pwOrg2.PublicId: {wantErr: handlers.ForbiddenError()},
oidcGlobal.PublicId: {wantErr: handlers.ForbiddenError()},
oidcOrg1.PublicId: {wantErr: handlers.ForbiddenError()},
oidcOrg2.PublicId: {wantErr: handlers.ForbiddenError()},
ldapGlobal.PublicId: {wantErr: handlers.ForbiddenError()},
ldapOrg1.PublicId: {wantErr: handlers.ForbiddenError()},
ldapOrg2.PublicId: {wantErr: handlers.ForbiddenError()},
},
},
{
name: "project role can't get auth methods (403)",
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: p1.PublicId,
Grants: []string{"ids=*;type=*;actions=*;output_fields=id,name,description"},
GrantScopes: []string{globals.GrantScopeThis},
},
}),
canGetAuthMethod: map[string]expectedOutput{
pwGlobal.PublicId: {wantErr: handlers.ForbiddenError()},
pwOrg1.PublicId: {wantErr: handlers.ForbiddenError()},
pwOrg2.PublicId: {wantErr: handlers.ForbiddenError()},
oidcGlobal.PublicId: {wantErr: handlers.ForbiddenError()},
oidcOrg1.PublicId: {wantErr: handlers.ForbiddenError()},
oidcOrg2.PublicId: {wantErr: handlers.ForbiddenError()},
ldapGlobal.PublicId: {wantErr: handlers.ForbiddenError()},
ldapOrg1.PublicId: {wantErr: handlers.ForbiddenError()},
ldapOrg2.PublicId: {wantErr: handlers.ForbiddenError()},
},
},
{
name: "global and org roles with id-only grants return only the granted resources",
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: globals.GlobalPrefix,
Grants: []string{"ids=" + pwGlobal.PublicId + ";actions=read;output_fields=id,name,description"},
GrantScopes: []string{globals.GrantScopeThis},
},
{
RoleScopeId: globals.GlobalPrefix,
Grants: []string{"ids=" + oidcOrg2.PublicId + ";actions=read;output_fields=id,name,description"},
GrantScopes: []string{globals.GrantScopeChildren},
},
{
RoleScopeId: org1.PublicId,
Grants: []string{"ids=" + ldapOrg1.PublicId + ";actions=read;output_fields=id,name,description"},
GrantScopes: []string{globals.GrantScopeThis},
},
}),
canGetAuthMethod: map[string]expectedOutput{
pwGlobal.PublicId: {wantOutfields: []string{globals.IdField, globals.ScopeIdField, globals.TypeField}},
pwOrg1.PublicId: {wantErr: handlers.ForbiddenError()},
pwOrg2.PublicId: {wantErr: handlers.ForbiddenError()},
oidcGlobal.PublicId: {wantErr: handlers.ForbiddenError()},
oidcOrg1.PublicId: {wantErr: handlers.ForbiddenError()},
oidcOrg2.PublicId: {wantOutfields: []string{globals.IdField, globals.ScopeIdField, globals.TypeField}},
ldapGlobal.PublicId: {wantErr: handlers.ForbiddenError()},
ldapOrg1.PublicId: {wantOutfields: []string{globals.IdField, globals.ScopeIdField, globals.TypeField}},
ldapOrg2.PublicId: {wantErr: 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 := controllerauth.TestAuthContextFromToken(t, conn, wrap, tok, iamRepo)
for id, expectedOutput := range tc.canGetAuthMethod {
input := &pbs.GetAuthMethodRequest{Id: id}
_, err := s.GetAuthMethod(fullGrantAuthCtx, input)
if expectedOutput.wantErr != nil {
require.ErrorIs(t, err, expectedOutput.wantErr)
continue
}
// check if the output fields are as expected
if tc.canGetAuthMethod[id].wantOutfields != nil {
handlers.TestAssertOutputFields(t, input, tc.canGetAuthMethod[id].wantOutfields)
}
require.NoError(t, err)
}
})
}
})
t.Run("authorized action", func(t *testing.T) {
input := &pbs.ListAuthMethodsRequest{
ScopeId: globals.GlobalPrefix,
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.GrantScopeThis, globals.GrantScopeDescendants},
},
})
// Call function to create direct/indirect relationship
user, account := userFunc()
// Create auth token for the user
tok, err := atRepo.CreateAuthToken(ctx, user, account.GetPublicId())
require.NoError(t, err)
// Create auth context from the auth token
fullGrantAuthCtx := controllerauth.TestAuthContextFromToken(t, conn, wrap, tok, iamRepo)
// auth method created during token generation will not be taken into considerations during this test
// adding to the ignoreList so it can be ignored later
ignoreList = append(ignoreList, tok.GetAuthMethodId())
got, finalErr := s.ListAuthMethods(fullGrantAuthCtx, input)
require.NoError(t, finalErr)
for _, item := range got.Items {
switch item.Type {
case "password":
require.ElementsMatch(t, item.AuthorizedActions, []string{"read", "delete", "authenticate", "no-op", "update"})
require.Len(t, item.AuthorizedCollectionActions, 2)
require.ElementsMatch(t, item.AuthorizedCollectionActions[resource.Account.PluralString()].AsSlice(), []string{"create", "list"})
require.ElementsMatch(t, item.AuthorizedCollectionActions[resource.ManagedGroup.PluralString()].AsSlice(), []string{"create", "list"})
case "ldap":
require.ElementsMatch(t, item.AuthorizedActions, []string{"read", "delete", "authenticate", "no-op", "update"})
require.Len(t, item.AuthorizedCollectionActions, 2)
require.ElementsMatch(t, item.AuthorizedCollectionActions[resource.Account.PluralString()].AsSlice(), []string{"create", "list"})
require.ElementsMatch(t, item.AuthorizedCollectionActions[resource.ManagedGroup.PluralString()].AsSlice(), []string{"create", "list"})
case "oidc":
require.ElementsMatch(t, item.AuthorizedActions, []string{"read", "delete", "authenticate", "change-state", "no-op", "update"})
require.Len(t, item.AuthorizedCollectionActions, 2)
require.ElementsMatch(t, item.AuthorizedCollectionActions[resource.Account.PluralString()].AsSlice(), []string{"create", "list"})
require.ElementsMatch(t, item.AuthorizedCollectionActions[resource.ManagedGroup.PluralString()].AsSlice(), []string{"create", "list"})
default:
t.Fatalf("unknown auth method type: %s", item.Type)
}
}
})
}
func TestGrants_Create(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
wrap := db.TestWrapper(t)
rw := db.New(conn)
kmsCache := kms.TestKms(t, conn, wrap)
iamRepo := iam.TestRepo(t, conn, wrap)
atRepo, err := authtoken.NewRepository(ctx, rw, rw, kmsCache)
require.NoError(t, err)
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
oidcRepoFn := func() (*oidc.Repository, error) {
return oidc.NewRepository(ctx, rw, rw, kmsCache)
}
ldapRepoFn := func() (*ldap.Repository, error) {
return ldap.NewRepository(ctx, rw, rw, kmsCache)
}
pwRepoFn := func() (*password.Repository, error) {
return password.NewRepository(ctx, rw, rw, kmsCache)
}
atRepoFn := func() (*authtoken.Repository, error) {
return atRepo, nil
}
authMethodRepoFn := func() (*auth.AuthMethodRepository, error) {
return auth.NewAuthMethodRepository(ctx, rw, rw, kmsCache)
}
s, err := authmethods.NewService(ctx,
kmsCache,
pwRepoFn,
oidcRepoFn,
iamRepoFn,
atRepoFn,
ldapRepoFn,
authMethodRepoFn,
1000)
require.NoError(t, err)
org1, p1 := iam.TestScopes(t, iamRepo)
org2, _ := iam.TestScopes(t, iamRepo)
testcases := []struct {
name string
userFunc func() (*iam.User, auth.Account)
canCreateInScope map[string]expectedOutput
}{
{
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=*;output_fields=id,scope_id,name,description,type,created_time,updated_time,version"},
GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren},
},
}),
canCreateInScope: map[string]expectedOutput{
globals.GlobalPrefix: {wantOutfields: []string{globals.IdField, globals.ScopeIdField, globals.NameField, globals.DescriptionField, globals.TypeField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField}},
org1.PublicId: {wantOutfields: []string{globals.IdField, globals.ScopeIdField, globals.NameField, globals.DescriptionField, globals.TypeField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField}},
org2.PublicId: {wantOutfields: []string{globals.IdField, globals.ScopeIdField, globals.NameField, globals.DescriptionField, globals.TypeField, globals.CreatedTimeField, globals.UpdatedTimeField, globals.VersionField}},
},
},
{
name: "global role grant children can only create org auth methods",
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: globals.GlobalPrefix,
Grants: []string{"ids=*;type=auth-method;actions=create;output_fields=id,scope_id,created_time"},
GrantScopes: []string{globals.GrantScopeChildren},
},
}),
canCreateInScope: map[string]expectedOutput{
globals.GlobalPrefix: {wantErr: handlers.ForbiddenError()},
org1.PublicId: {wantOutfields: []string{globals.IdField, globals.ScopeIdField, globals.CreatedTimeField}},
org2.PublicId: {wantOutfields: []string{globals.IdField, globals.ScopeIdField, globals.CreatedTimeField}},
},
},
{
name: "org role can't create global auth methods nor auth methods in other orgs",
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: org2.PublicId,
Grants: []string{"ids=*;type=auth-method;actions=create;output_fields=id,name,description"},
GrantScopes: []string{globals.GrantScopeThis},
},
}),
canCreateInScope: map[string]expectedOutput{
globals.GlobalPrefix: {wantErr: handlers.ForbiddenError()},
org1.PublicId: {wantErr: handlers.ForbiddenError()},
org2.PublicId: {wantOutfields: []string{globals.IdField, globals.NameField, globals.DescriptionField}},
},
},
{
name: "incorrect grants returns 403 error",
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: globals.GlobalPrefix,
Grants: []string{"ids=*;type=auth-method;actions=list,read,update;output_fields=id,scope_id"},
GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren},
},
{
RoleScopeId: org1.PublicId,
Grants: []string{"ids=*;type=auth-method;actions=list,read,update;output_fields=id,name"},
GrantScopes: []string{globals.GrantScopeThis},
},
}),
canCreateInScope: map[string]expectedOutput{
globals.GlobalPrefix: {wantErr: handlers.ForbiddenError()},
org1.PublicId: {wantErr: handlers.ForbiddenError()},
org2.PublicId: {wantErr: handlers.ForbiddenError()},
},
},
{
name: "no grants returns 403 error",
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, nil),
canCreateInScope: map[string]expectedOutput{
globals.GlobalPrefix: {wantErr: handlers.ForbiddenError()},
org1.PublicId: {wantErr: handlers.ForbiddenError()},
org2.PublicId: {wantErr: handlers.ForbiddenError()},
},
},
{
name: "project role can't create auth methods in any scope (403)",
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: p1.PublicId,
Grants: []string{"ids=*;type=*;actions=*"},
GrantScopes: []string{globals.GrantScopeThis},
},
}),
canCreateInScope: map[string]expectedOutput{
globals.GlobalPrefix: {wantErr: handlers.ForbiddenError()},
org1.PublicId: {wantErr: handlers.ForbiddenError()},
org2.PublicId: {wantErr: 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 := controllerauth.TestAuthContextFromToken(t, conn, wrap, tok, iamRepo)
for scopeId, expectedOutput := range tc.canCreateInScope {
// Build a distinct name for each Auth Method
name := fmt.Sprintf("[%s] ", tc.name)
switch scopeId {
case globals.GlobalPrefix:
name += "global"
case org1.PublicId:
name += "org 1"
case org2.PublicId:
name += "org 2"
default:
t.Fatalf("Unexpected scope ID: %s", scopeId)
}
resp, err := s.CreateAuthMethod(fullGrantAuthCtx, &pbs.CreateAuthMethodRequest{Item: &pb.AuthMethod{
Name: wrapperspb.String(name),
Description: wrapperspb.String("test description"),
ScopeId: scopeId,
Type: "password",
}})
if expectedOutput.wantErr != nil {
require.ErrorIs(t, err, expectedOutput.wantErr)
continue
}
require.NoError(t, err)
handlers.TestAssertOutputFields(t, resp.Item, expectedOutput.wantOutfields)
}
})
}
}
type directGrantUserAccountSetup func() (*iam.User, auth.Account)
func TestGrants_Update(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
wrap := db.TestWrapper(t)
rw := db.New(conn)
kmsCache := kms.TestKms(t, conn, wrap)
iamRepo := iam.TestRepo(t, conn, wrap)
atRepo, err := authtoken.NewRepository(ctx, rw, rw, kmsCache)
require.NoError(t, err)
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
oidcRepoFn := func() (*oidc.Repository, error) {
return oidc.NewRepository(ctx, rw, rw, kmsCache)
}
ldapRepoFn := func() (*ldap.Repository, error) {
return ldap.NewRepository(ctx, rw, rw, kmsCache)
}
pwRepoFn := func() (*password.Repository, error) {
return password.NewRepository(ctx, rw, rw, kmsCache)
}
atRepoFn := func() (*authtoken.Repository, error) {
return atRepo, nil
}
authMethodRepoFn := func() (*auth.AuthMethodRepository, error) {
return auth.NewAuthMethodRepository(ctx, rw, rw, kmsCache)
}
s, err := authmethods.NewService(ctx,
kmsCache,
pwRepoFn,
oidcRepoFn,
iamRepoFn,
atRepoFn,
ldapRepoFn,
authMethodRepoFn,
1000)
require.NoError(t, err)
org1, p1 := iam.TestScopes(t, iamRepo)
org2, _ := iam.TestScopes(t, iamRepo)
testcases := []struct {
name string
setupFunc func(t *testing.T) (directGrantUserAccountSetup, map[string]expectedOutput)
canUpdateAuthMethod func(t *testing.T) map[string]expectedOutput
}{
{
name: "global role grant this and children can update global auth method",
setupFunc: func(t *testing.T) (directGrantUserAccountSetup, map[string]expectedOutput) {
globalAmId := password.TestAuthMethod(t, conn, globals.GlobalPrefix).PublicId
org1AmId := password.TestAuthMethod(t, conn, org1.PublicId).PublicId
org2AmId := password.TestAuthMethod(t, conn, org2.PublicId).PublicId
wantOutput := map[string]expectedOutput{
globalAmId: {wantOutfields: []string{globals.IdField, globals.ScopeIdField, globals.NameField, globals.DescriptionField, globals.TypeField, globals.VersionField}},
org1AmId: {wantOutfields: []string{globals.IdField, globals.ScopeIdField, globals.NameField, globals.DescriptionField, globals.TypeField, globals.VersionField}},
org2AmId: {wantOutfields: []string{globals.IdField, globals.ScopeIdField, globals.NameField, globals.DescriptionField, globals.TypeField, globals.VersionField}},
}
userAccountFunc := iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: globals.GlobalPrefix,
Grants: []string{"ids=*;type=auth-method;actions=update;output_fields=id,scope_id,name,description,type,version"},
GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren},
},
})
return userAccountFunc, wantOutput
},
canUpdateAuthMethod: func(t *testing.T) map[string]expectedOutput {
globalAmId := password.TestAuthMethod(t, conn, globals.GlobalPrefix).PublicId
org1AmId := password.TestAuthMethod(t, conn, org1.PublicId).PublicId
org2AmId := password.TestAuthMethod(t, conn, org2.PublicId).PublicId
return map[string]expectedOutput{
globalAmId: {wantOutfields: []string{globals.IdField, globals.ScopeIdField, globals.NameField, globals.DescriptionField, globals.TypeField, globals.VersionField}},
org1AmId: {wantOutfields: []string{globals.IdField, globals.ScopeIdField, globals.NameField, globals.DescriptionField, globals.TypeField, globals.VersionField}},
org2AmId: {wantOutfields: []string{globals.IdField, globals.ScopeIdField, globals.NameField, globals.DescriptionField, globals.TypeField, globals.VersionField}},
}
},
},
{
name: "global role grant this & org role grant this can update their respective auth methods",
setupFunc: func(t *testing.T) (directGrantUserAccountSetup, map[string]expectedOutput) {
globalAmId := password.TestAuthMethod(t, conn, globals.GlobalPrefix).PublicId
org1AmId := password.TestAuthMethod(t, conn, org1.PublicId).PublicId
org2AmId := password.TestAuthMethod(t, conn, org2.PublicId).PublicId
wantOutput := map[string]expectedOutput{
globalAmId: {wantOutfields: []string{globals.IdField, globals.NameField, globals.DescriptionField, globals.VersionField}},
org1AmId: {wantOutfields: []string{globals.IdField, globals.ScopeIdField, globals.TypeField, globals.VersionField}},
org2AmId: {wantErr: handlers.ForbiddenError()},
}
userAccountFunc := iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: globals.GlobalPrefix,
Grants: []string{"ids=*;type=auth-method;actions=update;output_fields=id,name,description,version"},
GrantScopes: []string{globals.GrantScopeThis},
},
{
RoleScopeId: org1.PublicId,
Grants: []string{"ids=*;type=auth-method;actions=update;output_fields=id,scope_id,type,version"},
GrantScopes: []string{globals.GrantScopeThis},
},
})
return userAccountFunc, wantOutput
},
},
{
name: "org role can't update global auth methods",
setupFunc: func(t *testing.T) (directGrantUserAccountSetup, map[string]expectedOutput) {
globalAmId := password.TestAuthMethod(t, conn, globals.GlobalPrefix).PublicId
org1AmId := password.TestAuthMethod(t, conn, org1.PublicId).PublicId
org2AmId := password.TestAuthMethod(t, conn, org2.PublicId).PublicId
wantOutput := map[string]expectedOutput{
globalAmId: {wantErr: handlers.ForbiddenError()},
org1AmId: {wantOutfields: []string{globals.IdField, globals.VersionField, globals.CreatedTimeField, globals.UpdatedTimeField}},
org2AmId: {wantErr: handlers.ForbiddenError()},
}
userAccountFunc := iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: org1.PublicId,
Grants: []string{"ids=*;type=auth-method;actions=update;output_fields=id,version,created_time,updated_time"},
GrantScopes: []string{globals.GrantScopeThis},
},
})
return userAccountFunc, wantOutput
},
},
{
name: "global role grant children of global auth method's id can only update children auth methods",
setupFunc: func(t *testing.T) (directGrantUserAccountSetup, map[string]expectedOutput) {
globalAmId := password.TestAuthMethod(t, conn, globals.GlobalPrefix).PublicId
org1AmId := password.TestAuthMethod(t, conn, org1.PublicId).PublicId
org2AmId := password.TestAuthMethod(t, conn, org2.PublicId).PublicId
wantOutput := map[string]expectedOutput{
globalAmId: {wantErr: handlers.ForbiddenError()},
org1AmId: {wantOutfields: []string{globals.IdField}},
org2AmId: {wantErr: handlers.ForbiddenError()},
}
userAccountFunc := iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: globals.GlobalPrefix,
Grants: []string{
fmt.Sprintf("ids=%s;type=auth-method;actions=update;output_fields=id", org1AmId),
},
GrantScopes: []string{globals.GrantScopeChildren},
},
})
return userAccountFunc, wantOutput
},
},
{
name: "incorrect grants returns 403 error",
setupFunc: func(t *testing.T) (directGrantUserAccountSetup, map[string]expectedOutput) {
globalAmId := password.TestAuthMethod(t, conn, globals.GlobalPrefix).PublicId
org1AmId := password.TestAuthMethod(t, conn, org1.PublicId).PublicId
org2AmId := password.TestAuthMethod(t, conn, org2.PublicId).PublicId
wantOutput := map[string]expectedOutput{
globalAmId: {wantErr: handlers.ForbiddenError()},
org1AmId: {wantErr: handlers.ForbiddenError()},
org2AmId: {wantErr: handlers.ForbiddenError()},
}
userAccountFunc := iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: globals.GlobalPrefix,
Grants: []string{"ids=*;type=auth-method;actions=list,read,create"},
GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren},
},
{
RoleScopeId: org1.PublicId,
Grants: []string{"ids=*;type=auth-method;actions=list,read,create"},
GrantScopes: []string{globals.GrantScopeThis},
},
})
return userAccountFunc, wantOutput
},
},
{
name: "no grants returns 403 error",
setupFunc: func(t *testing.T) (directGrantUserAccountSetup, map[string]expectedOutput) {
globalAmId := password.TestAuthMethod(t, conn, globals.GlobalPrefix).PublicId
org1AmId := password.TestAuthMethod(t, conn, org1.PublicId).PublicId
org2AmId := password.TestAuthMethod(t, conn, org2.PublicId).PublicId
wantOutput := map[string]expectedOutput{
globalAmId: {wantErr: handlers.ForbiddenError()},
org1AmId: {wantErr: handlers.ForbiddenError()},
org2AmId: {wantErr: handlers.ForbiddenError()},
}
userAccountFunc := iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{})
return userAccountFunc, wantOutput
},
},
{
name: "project role can't update auth methods in any scope (403)",
setupFunc: func(t *testing.T) (directGrantUserAccountSetup, map[string]expectedOutput) {
globalAmId := password.TestAuthMethod(t, conn, globals.GlobalPrefix).PublicId
org1AmId := password.TestAuthMethod(t, conn, org1.PublicId).PublicId
org2AmId := password.TestAuthMethod(t, conn, org2.PublicId).PublicId
wantOutput := map[string]expectedOutput{
globalAmId: {wantErr: handlers.ForbiddenError()},
org1AmId: {wantErr: handlers.ForbiddenError()},
org2AmId: {wantErr: handlers.ForbiddenError()},
}
userAccountFunc := iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: p1.GetPublicId(),
Grants: []string{"ids=*;type=*;actions=*"},
GrantScopes: []string{globals.GrantScopeThis},
},
})
return userAccountFunc, wantOutput
},
},
}
for i, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
userAccountFunc, canUpdateAuthMethos := tc.setupFunc(t)
user, account := userAccountFunc()
tok, err := atRepo.CreateAuthToken(ctx, user, account.GetPublicId())
require.NoError(t, err)
fullGrantAuthCtx := controllerauth.TestAuthContextFromToken(t, conn, wrap, tok, iamRepo)
for amId, expectedOutput := range canUpdateAuthMethos {
resp, err := s.UpdateAuthMethod(fullGrantAuthCtx, &pbs.UpdateAuthMethodRequest{
Id: amId,
UpdateMask: &field_mask.FieldMask{
Paths: []string{"name", "description"},
},
Item: &pb.AuthMethod{
Name: &wrapperspb.StringValue{Value: fmt.Sprintf("updated name (%d)", i)},
Description: &wrapperspb.StringValue{Value: fmt.Sprintf("updated description (%d)", i)},
Version: uint32(1), // increment version to simulate an update
},
})
if expectedOutput.wantErr != nil {
require.ErrorIs(t, err, expectedOutput.wantErr)
continue
}
require.NoError(t, err)
handlers.TestAssertOutputFields(t, resp.Item, expectedOutput.wantOutfields)
}
})
}
}
func TestGrants_Delete(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
wrap := db.TestWrapper(t)
rw := db.New(conn)
kmsCache := kms.TestKms(t, conn, wrap)
iamRepo := iam.TestRepo(t, conn, wrap)
atRepo, err := authtoken.NewRepository(ctx, rw, rw, kmsCache)
require.NoError(t, err)
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
oidcRepoFn := func() (*oidc.Repository, error) {
return oidc.NewRepository(ctx, rw, rw, kmsCache)
}
ldapRepoFn := func() (*ldap.Repository, error) {
return ldap.NewRepository(ctx, rw, rw, kmsCache)
}
pwRepoFn := func() (*password.Repository, error) {
return password.NewRepository(ctx, rw, rw, kmsCache)
}
atRepoFn := func() (*authtoken.Repository, error) {
return atRepo, nil
}
authMethodRepoFn := func() (*auth.AuthMethodRepository, error) {
return auth.NewAuthMethodRepository(ctx, rw, rw, kmsCache)
}
s, err := authmethods.NewService(ctx,
kmsCache,
pwRepoFn,
oidcRepoFn,
iamRepoFn,
atRepoFn,
ldapRepoFn,
authMethodRepoFn,
1000)
require.NoError(t, err)
org1, p1 := iam.TestScopes(t, iamRepo)
org2, _ := iam.TestScopes(t, iamRepo)
allScopeIDs := []string{globals.GlobalPrefix, org1.PublicId, org2.PublicId}
testcases := []struct {
name string
am *password.AuthMethod
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.GrantScopeChildren},
},
}),
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},
},
{
name: "org role can't delete global auth methods",
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: org1.PublicId,
Grants: []string{"ids=*;type=auth-method;actions=delete"},
GrantScopes: []string{globals.GrantScopeThis},
},
}),
deleteAllowedAtScopeIDs: []string{org1.PublicId},
},
{
name: "incorrect grants returns 403 error",
am: password.TestAuthMethod(t, conn, org1.PublicId),
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: globals.GlobalPrefix,
Grants: []string{"ids=*;type=auth-method;actions=list,read,create,update"},
GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren},
},
}),
},
{
name: "no grants returns 403 error",
am: password.TestAuthMethod(t, conn, globals.GlobalPrefix),
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, nil),
},
{
name: "project role can't delete auth methods at any scope (403)",
am: password.TestAuthMethod(t, conn, globals.GlobalPrefix),
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: p1.PublicId,
Grants: []string{"ids=*;type=*;actions=*"},
GrantScopes: []string{globals.GrantScopeThis},
},
}),
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
// setup a map to track which scope correlates to an auth method
scopeIdAuthMethodMap := map[string]*password.AuthMethod{}
for _, scopeId := range allScopeIDs {
am := password.TestAuthMethod(t, conn, scopeId)
scopeIdAuthMethodMap[scopeId] = am
}
user, account := tc.userFunc()
tok, err := atRepo.CreateAuthToken(ctx, user, account.GetPublicId())
require.NoError(t, err)
fullGrantAuthCtx := controllerauth.TestAuthContextFromToken(t, conn, wrap, tok, iamRepo)
for scope, am := range scopeIdAuthMethodMap {
_, err = s.DeleteAuthMethod(fullGrantAuthCtx, &pbs.DeleteAuthMethodRequest{Id: am.PublicId})
if !slices.Contains(tc.deleteAllowedAtScopeIDs, scope) {
require.ErrorIs(t, err, handlers.ForbiddenError())
continue
}
require.NoErrorf(t, err, "failed to delete auth method in scope %s", scope)
}
})
}
}
func TestGrants_Authentication(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
wrap := db.TestWrapper(t)
rw := db.New(conn)
kmsCache := kms.TestKms(t, conn, wrap)
iamRepo := iam.TestRepo(t, conn, wrap)
atRepo, err := authtoken.NewRepository(ctx, rw, rw, kmsCache)
require.NoError(t, err)
pwRepo, err := password.NewRepository(ctx, rw, rw, kmsCache)
require.NoError(t, err)
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
oidcRepoFn := func() (*oidc.Repository, error) {
return oidc.NewRepository(ctx, rw, rw, kmsCache)
}
ldapRepoFn := func() (*ldap.Repository, error) {
return ldap.NewRepository(ctx, rw, rw, kmsCache)
}
pwRepoFn := func() (*password.Repository, error) {
return pwRepo, nil
}
atRepoFn := func() (*authtoken.Repository, error) {
return atRepo, nil
}
authMethodRepoFn := func() (*auth.AuthMethodRepository, error) {
return auth.NewAuthMethodRepository(ctx, rw, rw, kmsCache)
}
s, err := authmethods.NewService(ctx,
kmsCache,
pwRepoFn,
oidcRepoFn,
iamRepoFn,
atRepoFn,
ldapRepoFn,
authMethodRepoFn,
1000)
require.NoError(t, err)
org1, p1 := iam.TestScopes(t, iamRepo)
// We need a sys eventer in order to authenticate
eventConfig := event.TestEventerConfig(t, "TestGrants_Authentication", event.TestWithObservationSink(t))
testLock := &sync.Mutex{}
testLogger := hclog.New(&hclog.LoggerOptions{
Mutex: testLock,
Name: "test",
})
require.NoError(t, event.InitSysEventer(testLogger, testLock, "TestGrants_Authentication", event.WithEventerConfig(&eventConfig.EventerConfig)))
sinkFileName := eventConfig.ObservationEvents.Name()
t.Cleanup(func() {
require.NoError(t, os.Remove(sinkFileName))
event.TestResetSystEventer(t)
})
testcases := []struct {
name string
scopeId string
input *pbs.AuthenticateRequest
userFunc func() (*iam.User, auth.Account)
wantErr error
}{
{
name: "global role grant this and children can authenticate against a global auth method",
scopeId: globals.GlobalPrefix,
input: &pbs.AuthenticateRequest{
Type: "token",
Attrs: &pbs.AuthenticateRequest_PasswordLoginAttributes{
PasswordLoginAttributes: &pbs.PasswordLoginAttributes{
LoginName: testLoginName,
Password: testPassword,
},
},
},
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: globals.GlobalPrefix,
Grants: []string{"ids=*;type=auth-method;actions=authenticate"},
GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren},
},
}),
},
{
name: "org role can't authenticate against a global auth method",
scopeId: globals.GlobalPrefix,
input: &pbs.AuthenticateRequest{
Type: "token",
Attrs: &pbs.AuthenticateRequest_PasswordLoginAttributes{
PasswordLoginAttributes: &pbs.PasswordLoginAttributes{
LoginName: testLoginName,
Password: testPassword,
},
},
},
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: org1.PublicId,
Grants: []string{"ids=*;type=auth-method;actions=authenticate"},
GrantScopes: []string{globals.GrantScopeThis},
},
}),
wantErr: handlers.ForbiddenError(),
},
{
name: "no grants returns 403 error for a global auth method",
scopeId: globals.GlobalPrefix,
input: &pbs.AuthenticateRequest{
Type: "token",
Attrs: &pbs.AuthenticateRequest_PasswordLoginAttributes{
PasswordLoginAttributes: &pbs.PasswordLoginAttributes{
LoginName: testLoginName,
Password: testPassword,
},
},
},
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{}),
wantErr: handlers.ForbiddenError(),
},
{
name: "org auth methods, by default, grant anyone permission to authenticate (and list) auth methods",
scopeId: org1.PublicId,
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{}),
input: &pbs.AuthenticateRequest{
Type: "token",
Attrs: &pbs.AuthenticateRequest_PasswordLoginAttributes{
PasswordLoginAttributes: &pbs.PasswordLoginAttributes{
LoginName: testLoginName,
Password: testPassword,
},
},
},
},
{
name: "project role can authenticate against org auth methods because by default, org auth methods grant anyone permission to authenticate (and list) auth methods",
scopeId: org1.PublicId,
input: &pbs.AuthenticateRequest{
Type: "token",
Attrs: &pbs.AuthenticateRequest_PasswordLoginAttributes{
PasswordLoginAttributes: &pbs.PasswordLoginAttributes{
LoginName: testLoginName,
Password: testPassword,
},
},
},
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: p1.PublicId,
Grants: []string{"ids=*;type=auth-method;actions=authenticate"},
GrantScopes: []string{globals.GrantScopeThis},
},
}),
},
{
name: "granting authenticate again at the org scope allows authentication",
scopeId: org1.PublicId,
input: &pbs.AuthenticateRequest{
Type: "token",
Attrs: &pbs.AuthenticateRequest_PasswordLoginAttributes{
PasswordLoginAttributes: &pbs.PasswordLoginAttributes{
LoginName: testLoginName,
Password: testPassword,
},
},
},
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: org1.PublicId,
Grants: []string{"ids=*;type=auth-method;actions=authenticate"},
GrantScopes: []string{globals.GrantScopeThis},
},
}),
},
{
name: "project role can't authenticate against global auth methods",
scopeId: globals.GlobalPrefix,
input: &pbs.AuthenticateRequest{
Type: "token",
Attrs: &pbs.AuthenticateRequest_PasswordLoginAttributes{
PasswordLoginAttributes: &pbs.PasswordLoginAttributes{
LoginName: testLoginName,
Password: testPassword,
},
},
},
userFunc: iam.TestUserDirectGrantsFunc(t, conn, kmsCache, globals.GlobalPrefix, password.TestAuthMethodWithAccount, []iam.TestRoleGrantsRequest{
{
RoleScopeId: p1.PublicId,
Grants: []string{"ids=*;type=auth-method;actions=authenticate"},
GrantScopes: []string{globals.GrantScopeThis},
},
}),
wantErr: handlers.ForbiddenError(),
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
// set up an identity that we'll use to call authenticate request on. We'll use this credentials
// and auth method to make authenticate request on
tc.input.AuthMethodId = password.TestAuthMethod(t, conn, tc.scopeId).PublicId
newAcct, err := password.NewAccount(ctx, tc.input.AuthMethodId, password.WithLoginName(testLoginName))
require.NoError(t, err)
acctToLogin, err := pwRepo.CreateAccount(context.Background(), tc.scopeId, newAcct, password.WithPassword(testPassword))
require.NoError(t, err)
_ = iam.TestUser(t, iamRepo, tc.scopeId, iam.WithAccountIds(acctToLogin.PublicId))
// set up an identity that we'll use to call authenticate request
// Authentication API will rely on this user's grants to authorize the request
user, acctLoggingIn := tc.userFunc()
tok, err := atRepo.CreateAuthToken(ctx, user, acctLoggingIn.GetPublicId())
require.NoError(t, err)
fullGrantAuthCtx := controllerauth.TestAuthContextFromToken(t, conn, wrap, tok, iamRepo)
_, err = s.Authenticate(fullGrantAuthCtx, tc.input)
if tc.wantErr != nil {
require.ErrorIs(t, err, tc.wantErr)
return
}
require.NoError(t, err)
})
}
}