-
Notifications
You must be signed in to change notification settings - Fork 289
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(authmethods): set parent scope ID for auth methods resource (#5448)
* handlers/authmethods: fix children permission * documentation * formatting
- Loading branch information
Showing
2 changed files
with
267 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
266 changes: 266 additions & 0 deletions
266
internal/daemon/controller/handlers/authmethods/grants_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
}) | ||
} | ||
}) | ||
} |