Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(authmethods): set parent scope ID for auth methods resource #5448

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1577,6 +1577,7 @@ func newOutputOpts(ctx context.Context, item auth.AuthMethod, scopeInfoMap map[s
}
res.Id = item.GetPublicId()
res.ScopeId = item.GetScopeId()
res.ParentScopeId = scopeInfoMap[item.GetScopeId()].GetParentScopeId()
authorizedActions := authResults.FetchActionSetForId(ctx, item.GetPublicId(), IdActions[globals.ResourceInfoFromPrefix(item.GetPublicId()).Subtype], requestauth.WithResource(&res)).Strings()
if len(authorizedActions) == 0 {
return nil, false, nil
Expand Down
266 changes: 266 additions & 0 deletions internal/daemon/controller/handlers/authmethods/grants_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,266 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package authmethods_test

import (
"context"
"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"
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"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/require"
)

// 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)
iamRepo := iam.TestRepo(t, conn, wrap)
rw := db.New(conn)
kmsCache := kms.TestKms(t, conn, wrap)
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 authtoken.NewRepository(ctx, rw, rw, kmsCache)
}
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, _ := iam.TestScopes(t, iamRepo)
org2, _ := iam.TestScopes(t, iamRepo)
databaseWrapper, err := kmsCache.GetWrapper(context.Background(), globals.GlobalPrefix, kms.KeyPurposeDatabase)
require.NoError(t, err)
oidcGlobal := oidc.TestAuthMethod(t, conn, databaseWrapper, globals.GlobalPrefix, oidc.InactiveState, "client-id", "secret")
oidcOrg1 := oidc.TestAuthMethod(t, conn, databaseWrapper, org1.GetPublicId(), oidc.InactiveState, "client-id", "secret")
oidcOrg2 := oidc.TestAuthMethod(t, conn, databaseWrapper, org2.GetPublicId(), oidc.InactiveState, "client-id", "secret")

ldapGlobal := ldap.TestAuthMethod(t, conn, wrap, globals.GlobalPrefix, []string{"ldaps://alice.com"})
ldapOrg1 := ldap.TestAuthMethod(t, conn, wrap, org1.GetPublicId(), []string{"ldaps://alice.com"})
ldapOrg2 := ldap.TestAuthMethod(t, conn, wrap, org2.GetPublicId(), []string{"ldaps://alice.com"})

pwGlobal := password.TestAuthMethod(t, conn, globals.GlobalPrefix)
pwOrg1 := password.TestAuthMethod(t, conn, org1.GetPublicId())
pwOrg2 := password.TestAuthMethod(t, conn, org2.GetPublicId())

// 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
includeGlobalAuthMethods bool
rolesToCreate []authtoken.TestRoleGrantsForToken
wantErr error
wantIDs []string
}{
{
name: "global role grant this and children returns all auth methods",
input: &pbs.ListAuthMethodsRequest{
ScopeId: globals.GlobalPrefix,
Recursive: true,
},
rolesToCreate: []authtoken.TestRoleGrantsForToken{
{
RoleScopeID: globals.GlobalPrefix,
GrantStrings: []string{"ids=*;type=auth-method;actions=list,read"},
GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren},
},
},
wantErr: nil,
wantIDs: []string{
oidcGlobal.PublicId,
oidcOrg1.PublicId,
oidcOrg2.PublicId,
ldapGlobal.PublicId,
ldapOrg1.PublicId,
ldapOrg2.PublicId,
pwGlobal.PublicId,
pwOrg1.PublicId,
pwOrg2.PublicId,
},
},
{
name: "no grants return children org",
input: &pbs.ListAuthMethodsRequest{
ScopeId: globals.GlobalPrefix,
Recursive: true,
},
rolesToCreate: []authtoken.TestRoleGrantsForToken{},
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,
},
},
{
name: "org role grant this and children returns auth methods in org1",
input: &pbs.ListAuthMethodsRequest{
ScopeId: org1.PublicId,
Recursive: true,
},
rolesToCreate: []authtoken.TestRoleGrantsForToken{
{
RoleScopeID: org1.PublicId,
GrantStrings: []string{"ids=*;type=auth-method;actions=list,read"},
GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren},
},
},
wantErr: nil,
wantIDs: []string{oidcOrg1.PublicId, ldapOrg1.PublicId, pwOrg1.PublicId},
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
tok := authtoken.TestAuthTokenWithRoles(t, conn, kmsCache, globals.GlobalPrefix, tc.rolesToCreate)
// 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())
fullGrantAuthCtx := controllerauth.TestAuthContextFromToken(t, conn, wrap, tok, iamRepo)
got, finalErr := s.ListAuthMethods(fullGrantAuthCtx, tc.input)
if tc.wantErr != nil {
require.ErrorIs(t, finalErr, tc.wantErr)
return
}
// authtoken.TestAuthTokenWithRoles creates an auth method at `global` scope so
// include AuthMethodID of the user used to generate AuthToken in the "wantIDs" set
// when listing auth methods at global scope
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())
}
require.ElementsMatch(t, tc.wantIDs, gotIDs)
})
}
})

t.Run("Get", func(t *testing.T) {
testcases := []struct {
name string
input *pbs.ListAuthMethodsRequest
amIDExpectErrMap map[string]error
rolesToCreate []authtoken.TestRoleGrantsForToken
}{
{
name: "global role grant this and children returns all auth methods",
amIDExpectErrMap: map[string]error{
pwGlobal.GetPublicId(): nil,
pwOrg1.GetPublicId(): nil,
pwOrg2.GetPublicId(): nil,
},
rolesToCreate: []authtoken.TestRoleGrantsForToken{
{
RoleScopeID: globals.GlobalPrefix,
GrantStrings: []string{"ids=*;type=auth-method;actions=list,read"},
GrantScopes: []string{globals.GrantScopeThis, globals.GrantScopeChildren},
},
},
},
{
name: "org role grant this and children returns auth methods in org1",
input: &pbs.ListAuthMethodsRequest{
ScopeId: org1.PublicId,
Recursive: true,
},
rolesToCreate: []authtoken.TestRoleGrantsForToken{
{
RoleScopeID: globals.GlobalPrefix,
GrantStrings: []string{"ids=*;type=auth-method;actions=list,read"},
GrantScopes: []string{globals.GrantScopeChildren},
},
},
amIDExpectErrMap: map[string]error{
pwGlobal.GetPublicId(): handlers.ForbiddenError(),
pwOrg1.GetPublicId(): nil,
pwOrg2.GetPublicId(): nil,
},
},
{
name: "no grants return all auth methods",
input: &pbs.ListAuthMethodsRequest{
ScopeId: globals.GlobalPrefix,
Recursive: true,
PageSize: 500,
},
rolesToCreate: []authtoken.TestRoleGrantsForToken{},
amIDExpectErrMap: map[string]error{
pwGlobal.GetPublicId(): handlers.ForbiddenError(),
pwOrg1.GetPublicId(): handlers.ForbiddenError(),
pwOrg2.GetPublicId(): handlers.ForbiddenError(),
},
},
}

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
tok := authtoken.TestAuthTokenWithRoles(t, conn, kmsCache, globals.GlobalPrefix, tc.rolesToCreate)
fullGrantAuthCtx := controllerauth.TestAuthContextFromToken(t, conn, wrap, tok, iamRepo)
for amId, wantErr := range tc.amIDExpectErrMap {
_, err := s.GetAuthMethod(fullGrantAuthCtx, &pbs.GetAuthMethodRequest{
Id: amId,
})
if wantErr != nil {
require.ErrorIs(t, err, wantErr)
continue
}
require.NoError(t, err)
}
})
}
})
}
Loading