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/authtokens/authtoken_service_test.go

1005 lines
33 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package authtokens_test
import (
"context"
"errors"
"fmt"
"net/http/httptest"
"slices"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/auth/password"
"github.com/hashicorp/boundary/internal/authtoken"
"github.com/hashicorp/boundary/internal/daemon/controller/auth"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers"
"github.com/hashicorp/boundary/internal/daemon/controller/handlers/authtokens"
"github.com/hashicorp/boundary/internal/db"
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
authpb "github.com/hashicorp/boundary/internal/gen/controller/auth"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/requests"
"github.com/hashicorp/boundary/internal/server"
"github.com/hashicorp/boundary/internal/types/scope"
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/authtokens"
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/scopes"
"google.golang.org/grpc/codes"
"google.golang.org/protobuf/testing/protocmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
fullAuthorizedActions = []string{"no-op", "read", "read:self", "delete", "delete:self"}
selfAuthorizedActions = []string{"read:self", "delete:self"}
)
func TestGetSelf(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrap := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrap)
iamRepoFn := func() (*iam.Repository, error) {
return iam.TestRepo(t, conn, wrap), nil
}
tokenRepoFn := func() (*authtoken.Repository, error) {
return authtoken.NewRepository(ctx, rw, rw, kms)
}
serversRepoFn := func() (*server.Repository, error) {
return server.NewRepository(ctx, rw, rw, kms)
}
a, err := authtokens.NewService(ctx, tokenRepoFn, iamRepoFn, 1000)
require.NoError(t, err, "Couldn't create new auth token service.")
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
at1 := authtoken.TestAuthToken(t, conn, kms, o.GetPublicId())
at2 := authtoken.TestAuthToken(t, conn, kms, o.GetPublicId())
cases := []struct {
name string
token *authtoken.AuthToken
readId string
err error
}{
{
name: "at1 read self",
token: at1,
readId: at1.GetPublicId(),
},
{
name: "at1 read at2",
token: at1,
readId: at2.GetPublicId(),
err: handlers.ApiErrorWithCodeAndMessage(codes.PermissionDenied, "Forbidden."),
},
{
name: "at2 read self",
token: at2,
readId: at2.GetPublicId(),
},
{
name: "at2 read at1",
token: at2,
readId: at1.GetPublicId(),
err: handlers.ApiErrorWithCodeAndMessage(codes.PermissionDenied, "Forbidden."),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
require, assert := require.New(t), assert.New(t)
// Setup the auth request information
req := httptest.NewRequest("GET", fmt.Sprintf("http://127.0.0.1/v1/auth-tokens/%s", tc.readId), nil)
requestInfo := authpb.RequestInfo{
Path: req.URL.Path,
Method: req.Method,
TokenFormat: uint32(auth.AuthTokenTypeBearer),
PublicId: tc.token.GetPublicId(),
Token: tc.token.GetToken(),
}
ctx := auth.NewVerifierContext(ctx, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo)
ctx = context.WithValue(ctx, requests.ContextRequestInformationKey, &requests.RequestContext{})
got, err := a.GetAuthToken(ctx, &pbs.GetAuthTokenRequest{Id: tc.readId})
if tc.err != nil {
require.EqualError(err, tc.err.Error())
require.Nil(got)
return
}
require.NoError(err)
require.NotNil(got)
assert.Equal(tc.token.GetPublicId(), got.GetItem().GetId())
// Ensure we didn't simply have e.g. read on all tokens
assert.ElementsMatch([]string{"read:self", "delete:self"}, got.Item.GetAuthorizedActions())
})
}
}
func TestGet(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrap := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrap)
iamRepoFn := func() (*iam.Repository, error) {
return iam.TestRepo(t, conn, wrap), nil
}
repoFn := func() (*authtoken.Repository, error) {
return authtoken.NewRepository(ctx, rw, rw, kms)
}
s, err := authtokens.NewService(ctx, repoFn, iamRepoFn, 1000)
require.NoError(t, err, "Couldn't create new auth token service.")
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId())
wireAuthToken := pb.AuthToken{
Id: at.GetPublicId(),
ScopeId: at.GetScopeId(),
UserId: at.GetIamUserId(),
AuthMethodId: at.GetAuthMethodId(),
AccountId: at.GetAuthAccountId(),
CreatedTime: at.GetCreateTime().GetTimestamp(),
UpdatedTime: at.GetUpdateTime().GetTimestamp(),
ApproximateLastUsedTime: at.GetApproximateLastAccessTime().GetTimestamp(),
ExpirationTime: at.GetExpirationTime().GetTimestamp(),
Scope: &scopes.ScopeInfo{Id: org.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
AuthorizedActions: fullAuthorizedActions,
}
cases := []struct {
name string
req *pbs.GetAuthTokenRequest
res *pbs.GetAuthTokenResponse
err error
}{
{
name: "Get an existing auth token",
req: &pbs.GetAuthTokenRequest{Id: wireAuthToken.GetId()},
res: &pbs.GetAuthTokenResponse{Item: &wireAuthToken},
},
{
name: "Get a non existing auth token",
req: &pbs.GetAuthTokenRequest{Id: globals.AuthTokenPrefix + "_DoesntExis"},
res: nil,
err: handlers.ApiErrorWithCode(codes.NotFound),
},
{
name: "Wrong id prefix",
req: &pbs.GetAuthTokenRequest{Id: "j_1234567890"},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
{
name: "space in id",
req: &pbs.GetAuthTokenRequest{Id: globals.AuthTokenPrefix + "_1 23456789"},
res: nil,
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, gErr := s.GetAuthToken(auth.DisabledAuthTestContext(iamRepoFn, org.GetPublicId()), tc.req)
if tc.err != nil {
require.Error(gErr)
assert.True(errors.Is(gErr, tc.err), "GetAuthToken(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
}
assert.Empty(cmp.Diff(
got,
tc.res,
protocmp.Transform(),
cmpopts.SortSlices(func(a, b string) bool {
return a < b
}),
), "GetAuthToken(%q) got response %q, wanted %q", tc.req, got, tc.res)
})
}
}
func TestList_Self(t *testing.T) {
testCtx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrap := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrap)
iamRepo := iam.TestRepo(t, conn, wrap)
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
tokenRepoFn := func() (*authtoken.Repository, error) {
return authtoken.NewRepository(testCtx, rw, rw, kms)
}
serversRepoFn := func() (*server.Repository, error) {
return server.NewRepository(testCtx, rw, rw, kms)
}
// This will result in the scope having default permissions, which now
// includes list on auth tokens
o, _ := iam.TestScopes(t, iamRepo)
// Each of these should only end up being able to list themselves
at := authtoken.TestAuthToken(t, conn, kms, o.GetPublicId())
otherAt := authtoken.TestAuthToken(t, conn, kms, o.GetPublicId())
cases := []struct {
name string
requester *authtoken.AuthToken
req *pbs.ListAuthTokensRequest
count int
}{
{
name: "First token sees only self",
req: &pbs.ListAuthTokensRequest{ScopeId: o.GetPublicId()},
requester: at,
},
{
name: "Second token sees only self",
req: &pbs.ListAuthTokensRequest{ScopeId: o.GetPublicId()},
requester: otherAt,
},
}
a, err := authtokens.NewService(testCtx, tokenRepoFn, iamRepoFn, 1000)
require.NoError(t, err)
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
require, assert := require.New(t), assert.New(t)
// Setup the auth request information
req := httptest.NewRequest("GET", fmt.Sprintf("http://127.0.0.1/v1/auth-tokens?scope_id=%s", o.GetPublicId()), nil)
requestInfo := authpb.RequestInfo{
Path: req.URL.Path,
Method: req.Method,
TokenFormat: uint32(auth.AuthTokenTypeBearer),
PublicId: tc.requester.GetPublicId(),
Token: tc.requester.GetToken(),
}
ctx := auth.NewVerifierContext(testCtx, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo)
got, err := a.ListAuthTokens(ctx, tc.req)
require.NoError(err)
require.Len(got.Items, 1)
assert.Equal(got.Items[0].GetId(), tc.requester.GetPublicId())
// Ensure we didn't simply have e.g. read on all tokens
assert.ElementsMatch(got.Items[0].GetAuthorizedActions(), []string{"read:self", "delete:self"})
})
}
}
func TestList(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
sqlDB, err := conn.SqlDB(context.Background())
require.NoError(t, err)
rw := db.New(conn)
wrap := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrap)
iamRepoFn := func() (*iam.Repository, error) {
return iam.TestRepo(t, conn, wrap), nil
}
repoFn := func() (*authtoken.Repository, error) {
return authtoken.NewRepository(context.Background(), rw, rw, kms)
}
iamRepo := iam.TestRepo(t, conn, wrap)
orgNoTokens, _ := iam.TestScopes(t, iamRepo)
var globalTokens []*pb.AuthToken
var allTokens []*pb.AuthToken
for i := 0; i < 3; i++ {
at := authtoken.TestAuthToken(t, conn, kms, scope.Global.String())
pbat := &pb.AuthToken{
Id: at.GetPublicId(),
ScopeId: at.GetScopeId(),
UserId: at.GetIamUserId(),
AuthMethodId: at.GetAuthMethodId(),
AccountId: at.GetAuthAccountId(),
CreatedTime: at.GetCreateTime().GetTimestamp(),
UpdatedTime: at.GetUpdateTime().GetTimestamp(),
ApproximateLastUsedTime: at.GetApproximateLastAccessTime().GetTimestamp(),
ExpirationTime: at.GetExpirationTime().GetTimestamp(),
Scope: &scopes.ScopeInfo{Id: scope.Global.String(), Type: scope.Global.String(), Name: scope.Global.String(), Description: "Global Scope"},
AuthorizedActions: fullAuthorizedActions,
}
globalTokens = append(globalTokens, pbat)
allTokens = append(allTokens, pbat)
}
orgWithSomeTokens, _ := iam.TestScopes(t, iamRepo)
var wantSomeTokens []*pb.AuthToken
for i := 0; i < 3; i++ {
at := authtoken.TestAuthToken(t, conn, kms, orgWithSomeTokens.GetPublicId())
pbat := &pb.AuthToken{
Id: at.GetPublicId(),
ScopeId: at.GetScopeId(),
UserId: at.GetIamUserId(),
AuthMethodId: at.GetAuthMethodId(),
AccountId: at.GetAuthAccountId(),
CreatedTime: at.GetCreateTime().GetTimestamp(),
UpdatedTime: at.GetUpdateTime().GetTimestamp(),
ApproximateLastUsedTime: at.GetApproximateLastAccessTime().GetTimestamp(),
ExpirationTime: at.GetExpirationTime().GetTimestamp(),
Scope: &scopes.ScopeInfo{Id: orgWithSomeTokens.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
AuthorizedActions: fullAuthorizedActions,
}
wantSomeTokens = append(wantSomeTokens, pbat)
allTokens = append(allTokens, pbat)
}
orgWithOtherTokens, _ := iam.TestScopes(t, iamRepo)
var wantOtherTokens []*pb.AuthToken
for i := 0; i < 3; i++ {
at := authtoken.TestAuthToken(t, conn, kms, orgWithOtherTokens.GetPublicId())
pbat := &pb.AuthToken{
Id: at.GetPublicId(),
ScopeId: at.GetScopeId(),
UserId: at.GetIamUserId(),
AuthMethodId: at.GetAuthMethodId(),
AccountId: at.GetAuthAccountId(),
CreatedTime: at.GetCreateTime().GetTimestamp(),
UpdatedTime: at.GetUpdateTime().GetTimestamp(),
ApproximateLastUsedTime: at.GetApproximateLastAccessTime().GetTimestamp(),
ExpirationTime: at.GetExpirationTime().GetTimestamp(),
Scope: &scopes.ScopeInfo{Id: orgWithOtherTokens.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()},
AuthorizedActions: fullAuthorizedActions,
}
wantOtherTokens = append(wantOtherTokens, pbat)
allTokens = append(allTokens, pbat)
}
// Reverse slices since response is ordered by created_time descending (newest first)
slices.Reverse(globalTokens)
slices.Reverse(wantSomeTokens)
slices.Reverse(wantOtherTokens)
slices.Reverse(allTokens)
// Run analyze to update postgres estimates
_, err = sqlDB.ExecContext(context.Background(), "analyze")
require.NoError(t, err)
cases := []struct {
name string
req *pbs.ListAuthTokensRequest
res *pbs.ListAuthTokensResponse
err error
}{
{
name: "List Some Tokens",
req: &pbs.ListAuthTokensRequest{ScopeId: orgWithSomeTokens.GetPublicId()},
res: &pbs.ListAuthTokensResponse{
Items: wantSomeTokens,
EstItemCount: 3,
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
},
},
{
name: "List Other Tokens",
req: &pbs.ListAuthTokensRequest{ScopeId: orgWithOtherTokens.GetPublicId()},
res: &pbs.ListAuthTokensResponse{
Items: wantOtherTokens,
EstItemCount: 3,
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
},
},
{
name: "List No Token",
req: &pbs.ListAuthTokensRequest{ScopeId: orgNoTokens.GetPublicId()},
res: &pbs.ListAuthTokensResponse{
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
},
},
// TODO: When an org doesn't exist, we should return a 404 instead of an empty list.
{
name: "Unfound Org",
req: &pbs.ListAuthTokensRequest{ScopeId: scope.Org.Prefix() + "_DoesntExis"},
err: handlers.ApiErrorWithCode(codes.NotFound),
},
{
name: "List Recursively",
req: &pbs.ListAuthTokensRequest{ScopeId: "global", Recursive: true},
res: &pbs.ListAuthTokensResponse{
Items: allTokens,
EstItemCount: 9,
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
},
},
{
name: "Paginate listing",
req: &pbs.ListAuthTokensRequest{ScopeId: "global", Recursive: true, PageSize: 2},
res: &pbs.ListAuthTokensResponse{
Items: allTokens[:2],
EstItemCount: 9,
ResponseType: "delta",
SortBy: "created_time",
SortDir: "desc",
},
},
{
name: "Filter to Some Tokens",
req: &pbs.ListAuthTokensRequest{
ScopeId: "global", Recursive: true,
Filter: fmt.Sprintf(`"/item/scope/id"==%q`, orgWithSomeTokens.GetPublicId()),
},
res: &pbs.ListAuthTokensResponse{
Items: wantSomeTokens,
EstItemCount: 3,
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
},
},
{
name: "Filter All Tokens",
req: &pbs.ListAuthTokensRequest{ScopeId: orgWithOtherTokens.GetPublicId(), Filter: `"/item/scope/id"=="thisdoesntmatch"`},
res: &pbs.ListAuthTokensResponse{
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
},
},
{
name: "Filter Bad Format",
req: &pbs.ListAuthTokensRequest{ScopeId: orgWithSomeTokens.GetPublicId(), Filter: `"//id/"=="bad"`},
err: handlers.InvalidArgumentErrorf("bad format", nil),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
s, err := authtokens.NewService(context.Background(), repoFn, iamRepoFn, 1000)
assert, require := assert.New(t), require.New(t)
require.NoError(err, "Couldn't create new user service.")
// Check non-anon listing
got, gErr := s.ListAuthTokens(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId(), auth.WithUserId(globals.AnyAuthenticatedUserId)), tc.req)
if tc.err != nil {
require.Error(gErr)
assert.True(errors.Is(gErr, tc.err), "ListAuthTokens() with scope %q got error %v, wanted %v", tc.req.GetScopeId(), gErr, tc.err)
return
} else {
require.NoError(gErr)
}
assert.Empty(cmp.Diff(
got,
tc.res,
protocmp.Transform(),
protocmp.SortRepeatedFields(got),
cmpopts.SortSlices(func(a, b string) bool {
return a < b
}),
protocmp.IgnoreFields(&pbs.ListAuthTokensResponse{}, "list_token"),
))
// Now check anon listing
got, gErr = s.ListAuthTokens(auth.DisabledAuthTestContext(iamRepoFn, tc.req.GetScopeId(), auth.WithUserId(globals.AnonymousUserId)), tc.req)
require.NoError(gErr)
assert.Len(got.Items, len(tc.res.Items))
for _, item := range got.GetItems() {
require.Nil(item.CreatedTime)
require.Nil(item.ExpirationTime)
require.Nil(item.UpdatedTime)
require.Nil(item.ApproximateLastUsedTime)
}
})
}
}
func authTokenToProto(at *authtoken.AuthToken, scope *scopes.ScopeInfo, authorizedActions []string) *pb.AuthToken {
return &pb.AuthToken{
Id: at.GetPublicId(),
ScopeId: at.GetScopeId(),
UserId: at.GetIamUserId(),
AuthMethodId: at.GetAuthMethodId(),
AccountId: at.GetAuthAccountId(),
CreatedTime: at.GetCreateTime().GetTimestamp(),
UpdatedTime: at.GetUpdateTime().GetTimestamp(),
ApproximateLastUsedTime: at.GetApproximateLastAccessTime().GetTimestamp(),
ExpirationTime: at.GetExpirationTime().GetTimestamp(),
Scope: scope,
AuthorizedActions: authorizedActions,
}
}
func TestListPagination(t *testing.T) {
// Set database read timeout to avoid duplicates in response
oldReadTimeout := globals.RefreshReadLookbackDuration
globals.RefreshReadLookbackDuration = 0
t.Cleanup(func() {
globals.RefreshReadLookbackDuration = oldReadTimeout
})
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
sqlDB, err := conn.SqlDB(ctx)
require.NoError(t, err)
rw := db.New(conn)
wrap := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrap)
iamRepoFn := func() (*iam.Repository, error) {
return iam.TestRepo(t, conn, wrap), nil
}
tokenRepoFn := func() (*authtoken.Repository, error) {
return authtoken.NewRepository(ctx, rw, rw, kms)
}
serversRepoFn := func() (*server.Repository, error) {
return server.NewRepository(ctx, rw, rw, kms)
}
iamRepo := iam.TestRepo(t, conn, wrap)
tokenRepo, _ := tokenRepoFn()
orgWithTokens, pwt := iam.TestScopes(t, iamRepo)
authMethod := password.TestAuthMethods(t, conn, orgWithTokens.GetPublicId(), 1)[0]
// auth account is only used to join auth method to user.
// We don't do anything else with the auth account in the test setup.
acct := password.TestAccount(t, conn, authMethod.GetPublicId(), "test_user")
u := iam.TestUser(t, iamRepo, orgWithTokens.GetPublicId(), iam.WithAccountIds(acct.PublicId))
privProjRole := iam.TestRole(t, conn, pwt.GetPublicId())
iam.TestRoleGrant(t, conn, privProjRole.GetPublicId(), "ids=*;type=*;actions=*")
iam.TestUserRole(t, conn, privProjRole.GetPublicId(), u.GetPublicId())
var allTokens []*pb.AuthToken
for i := 0; i < 9; i++ {
at, _ := tokenRepo.CreateAuthToken(ctx, u, acct.GetPublicId())
atp := authTokenToProto(at, &scopes.ScopeInfo{Id: orgWithTokens.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()}, selfAuthorizedActions)
allTokens = append(allTokens, atp)
}
a, err := authtokens.NewService(ctx, tokenRepoFn, iamRepoFn, 1000)
require.NoError(t, err, "Couldn't create new user service.")
masterToken, _ := tokenRepo.CreateAuthToken(ctx, u, acct.GetPublicId())
mtp := authTokenToProto(masterToken, &scopes.ScopeInfo{Id: orgWithTokens.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()}, selfAuthorizedActions)
allTokens = append(allTokens, mtp)
slices.Reverse(allTokens)
// Run analyze to update postgres estimates
_, err = sqlDB.ExecContext(context.Background(), "analyze")
require.NoError(t, err)
requestInfo := authpb.RequestInfo{
TokenFormat: uint32(auth.AuthTokenTypeBearer),
Token: masterToken.GetToken(),
PublicId: masterToken.GetPublicId(),
}
requestContext := context.WithValue(ctx, requests.ContextRequestInformationKey, &requests.RequestContext{})
ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo)
// Start paginating, recursively
req := &pbs.ListAuthTokensRequest{
ScopeId: "global",
Recursive: true,
Filter: "",
ListToken: "",
PageSize: 2,
}
got, err := a.ListAuthTokens(ctx, req)
require.NoError(t, err)
require.Len(t, got.GetItems(), 2)
// Compare without comparing the list token
assert.Empty(t,
cmp.Diff(
got,
&pbs.ListAuthTokensResponse{
Items: allTokens[0:2],
ResponseType: "delta",
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: 10,
},
cmpopts.SortSlices(func(a, b string) bool {
return a < b
}),
protocmp.Transform(),
protocmp.IgnoreFields(&pbs.ListAuthTokensResponse{}, "list_token"),
),
)
// Request second page
req.ListToken = got.ListToken
got, err = a.ListAuthTokens(ctx, req)
require.NoError(t, err)
require.Len(t, got.GetItems(), 2)
// Compare without comparing the list token
assert.Empty(t,
cmp.Diff(
got,
&pbs.ListAuthTokensResponse{
Items: allTokens[2:4],
ResponseType: "delta",
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: 10,
},
cmpopts.SortSlices(func(a, b string) bool {
return a < b
}),
protocmp.Transform(),
protocmp.IgnoreFields(&pbs.ListAuthTokensResponse{}, "list_token"),
),
)
// Request rest of results
req.ListToken = got.ListToken
req.PageSize = 10
got, err = a.ListAuthTokens(ctx, req)
require.NoError(t, err)
require.Len(t, got.GetItems(), 6)
// Compare without comparing the list token
assert.Empty(t,
cmp.Diff(
got,
&pbs.ListAuthTokensResponse{
Items: allTokens[4:],
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: 10,
},
cmpopts.SortSlices(func(a, b string) bool {
return a < b
}),
protocmp.Transform(),
protocmp.IgnoreFields(&pbs.ListAuthTokensResponse{}, "list_token"),
),
)
// Create two auth tokens in lieu of updating
at1, _ := tokenRepo.CreateAuthToken(ctx, u, acct.GetPublicId())
atp1 := authTokenToProto(at1, &scopes.ScopeInfo{Id: orgWithTokens.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()}, selfAuthorizedActions)
// Add to the front since it's most recently updated
allTokens = append([]*pb.AuthToken{atp1}, allTokens...)
at2, _ := tokenRepo.CreateAuthToken(ctx, u, acct.GetPublicId())
atp2 := authTokenToProto(at2, &scopes.ScopeInfo{Id: orgWithTokens.GetPublicId(), Type: scope.Org.String(), ParentScopeId: scope.Global.String()}, selfAuthorizedActions)
// Add to the front since it's most recently updated
allTokens = append([]*pb.AuthToken{atp2}, allTokens...)
// Delete one of the other auth tokens
_, err = tokenRepo.DeleteAuthToken(ctx, allTokens[len(allTokens)-1].Id)
require.NoError(t, err)
deletedAuthToken := allTokens[len(allTokens)-1]
allTokens = allTokens[:len(allTokens)-1]
// Run analyze to update postgres estimates
_, err = sqlDB.ExecContext(ctx, "analyze")
require.NoError(t, err)
// Request updated results
req.ListToken = got.ListToken
req.PageSize = 1
got, err = a.ListAuthTokens(ctx, req)
require.NoError(t, err)
require.Len(t, got.GetItems(), 1)
// Compare without comparing the list token
assert.Empty(t,
cmp.Diff(
got,
&pbs.ListAuthTokensResponse{
Items: []*pb.AuthToken{allTokens[0]},
ResponseType: "delta",
SortBy: "updated_time",
SortDir: "desc",
// Should contain the deleted auth token
RemovedIds: []string{deletedAuthToken.Id},
EstItemCount: 11,
},
cmpopts.SortSlices(func(a, b string) bool {
return a < b
}),
protocmp.Transform(),
protocmp.IgnoreFields(&pbs.ListAuthTokensResponse{}, "list_token"),
),
)
// Get next page
req.ListToken = got.ListToken
got, err = a.ListAuthTokens(ctx, req)
require.NoError(t, err)
require.Len(t, got.GetItems(), 1)
// Compare without comparing the list token
assert.Empty(t,
cmp.Diff(
got,
&pbs.ListAuthTokensResponse{
Items: []*pb.AuthToken{allTokens[1]},
ResponseType: "complete",
SortBy: "updated_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: 11,
},
cmpopts.SortSlices(func(a, b string) bool {
return a < b
}),
protocmp.Transform(),
protocmp.IgnoreFields(&pbs.ListAuthTokensResponse{}, "list_token"),
),
)
// Request new page with filter requiring looping
// to fill the page.
req.ListToken = ""
req.PageSize = 1
req.Filter = fmt.Sprintf(`"/item/id"==%q or "/item/id"==%q`, allTokens[len(allTokens)-2].Id, allTokens[len(allTokens)-1].Id)
got, err = a.ListAuthTokens(ctx, req)
require.NoError(t, err)
require.Len(t, got.GetItems(), 1)
assert.Empty(t,
cmp.Diff(
got,
&pbs.ListAuthTokensResponse{
Items: []*pb.AuthToken{allTokens[len(allTokens)-2]},
ResponseType: "delta",
SortBy: "created_time",
SortDir: "desc",
// Should be empty again
RemovedIds: nil,
EstItemCount: 11,
},
cmpopts.SortSlices(func(a, b string) bool {
return a < b
}),
protocmp.Transform(),
protocmp.IgnoreFields(&pbs.ListAuthTokensResponse{}, "list_token"),
),
)
req.ListToken = got.ListToken
// Get the second page
got, err = a.ListAuthTokens(ctx, req)
require.NoError(t, err)
require.Len(t, got.GetItems(), 1)
assert.Empty(t,
cmp.Diff(
got,
&pbs.ListAuthTokensResponse{
Items: []*pb.AuthToken{allTokens[len(allTokens)-1]},
ResponseType: "complete",
SortBy: "created_time",
SortDir: "desc",
RemovedIds: nil,
EstItemCount: 11,
},
cmpopts.SortSlices(func(a, b string) bool {
return a < b
}),
protocmp.Transform(),
protocmp.IgnoreFields(&pbs.ListAuthTokensResponse{}, "list_token"),
),
)
// Create unauthenticated user
unauthAt := authtoken.TestAuthToken(t, conn, kms, orgWithTokens.GetPublicId())
unauthR := iam.TestRole(t, conn, pwt.GetPublicId())
_ = iam.TestUserRole(t, conn, unauthR.GetPublicId(), unauthAt.GetIamUserId())
// Make a request with the unauthenticated user,
// ensure the response contains the pagination parameters.
requestInfo = authpb.RequestInfo{
TokenFormat: uint32(auth.AuthTokenTypeBearer),
PublicId: unauthAt.GetPublicId(),
Token: unauthAt.GetToken(),
}
requestContext = context.WithValue(context.Background(), requests.ContextRequestInformationKey, &requests.RequestContext{})
ctx = auth.NewVerifierContext(requestContext, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo)
got, err = a.ListAuthTokens(ctx, &pbs.ListAuthTokensRequest{
ScopeId: "global",
Recursive: true,
})
require.NoError(t, err)
assert.Len(t, got.Items, 1) // There will always be at least one token, the token used to authenticate
assert.Equal(t, "created_time", got.SortBy)
assert.Equal(t, "desc", got.SortDir)
assert.Equal(t, "complete", got.ResponseType)
}
func TestDeleteSelf(t *testing.T) {
testCtx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrap := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrap)
iamRepo := iam.TestRepo(t, conn, wrap)
iamRepoFn := func() (*iam.Repository, error) {
return iamRepo, nil
}
tokenRepoFn := func() (*authtoken.Repository, error) {
return authtoken.NewRepository(testCtx, rw, rw, kms)
}
serversRepoFn := func() (*server.Repository, error) {
return server.NewRepository(testCtx, rw, rw, kms)
}
a, err := authtokens.NewService(testCtx, tokenRepoFn, iamRepoFn, 1000)
require.NoError(t, err, "Couldn't create new auth token service.")
o, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrap))
at1 := authtoken.TestAuthToken(t, conn, kms, o.GetPublicId())
at2 := authtoken.TestAuthToken(t, conn, kms, o.GetPublicId())
cases := []struct {
name string
token *authtoken.AuthToken
deleteId string
err error
}{
{
name: "at1 delete at2",
token: at1,
deleteId: at2.GetPublicId(),
err: handlers.ApiErrorWithCodeAndMessage(codes.PermissionDenied, "Forbidden."),
},
{
name: "at2 delete at1",
token: at2,
deleteId: at1.GetPublicId(),
err: handlers.ApiErrorWithCodeAndMessage(codes.PermissionDenied, "Forbidden."),
},
{
name: "at1 delete self",
token: at1,
deleteId: at1.GetPublicId(),
},
{
name: "at2 delete self",
token: at2,
deleteId: at2.GetPublicId(),
},
{
name: "at1 not found",
token: at1,
deleteId: at1.GetPublicId(),
err: handlers.ApiErrorWithCodeAndMessage(codes.NotFound, "Resource not found."),
},
{
name: "at2 not found",
token: at2,
deleteId: at2.GetPublicId(),
err: handlers.ApiErrorWithCodeAndMessage(codes.NotFound, "Resource not found."),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
require := require.New(t)
// Setup the auth request information
req := httptest.NewRequest("DELETE", fmt.Sprintf("http://127.0.0.1/v1/auth-tokens/%s", tc.deleteId), nil)
requestInfo := authpb.RequestInfo{
Path: req.URL.Path,
Method: req.Method,
TokenFormat: uint32(auth.AuthTokenTypeBearer),
PublicId: tc.token.GetPublicId(),
Token: tc.token.GetToken(),
}
ctx := auth.NewVerifierContext(testCtx, iamRepoFn, tokenRepoFn, serversRepoFn, kms, &requestInfo)
got, err := a.DeleteAuthToken(ctx, &pbs.DeleteAuthTokenRequest{Id: tc.deleteId})
if tc.err != nil {
require.EqualError(err, tc.err.Error())
require.Nil(got)
return
}
require.NoError(err)
})
}
}
func TestDelete(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrap := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrap)
iamRepoFn := func() (*iam.Repository, error) {
return iam.TestRepo(t, conn, wrap), nil
}
repoFn := func() (*authtoken.Repository, error) {
return authtoken.NewRepository(ctx, rw, rw, kms)
}
iamRepo := iam.TestRepo(t, conn, wrap)
org, _ := iam.TestScopes(t, iamRepo)
at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId())
s, err := authtokens.NewService(ctx, repoFn, iamRepoFn, 1000)
require.NoError(t, err, "Error when getting new user service.")
cases := []struct {
name string
scope string
req *pbs.DeleteAuthTokenRequest
res *pbs.DeleteAuthTokenResponse
err error
}{
{
name: "Delete an existing token",
scope: org.GetPublicId(),
req: &pbs.DeleteAuthTokenRequest{
Id: at.GetPublicId(),
},
},
{
name: "Delete bad token id",
scope: org.GetPublicId(),
req: &pbs.DeleteAuthTokenRequest{
Id: globals.AuthTokenPrefix + "_doesntexis",
},
err: handlers.ApiErrorWithCode(codes.NotFound),
},
{
name: "Bad token id formatting",
scope: org.GetPublicId(),
req: &pbs.DeleteAuthTokenRequest{
Id: "bad_format",
},
err: handlers.ApiErrorWithCode(codes.InvalidArgument),
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, gErr := s.DeleteAuthToken(auth.DisabledAuthTestContext(iamRepoFn, tc.scope), tc.req)
if tc.err != nil {
require.Error(gErr)
assert.True(errors.Is(gErr, tc.err), "DeleteAuthToken(%+v) got error %v, wanted %v", tc.req, gErr, tc.err)
}
assert.EqualValuesf(tc.res, got, "DeleteAuthToken(%q) got response %q, wanted %q", tc.req, got, tc.res)
})
}
}
func TestDelete_twice(t *testing.T) {
assert, require := assert.New(t), require.New(t)
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrap := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrap)
iamRepoFn := func() (*iam.Repository, error) {
return iam.TestRepo(t, conn, wrap), nil
}
repoFn := func() (*authtoken.Repository, error) {
return authtoken.NewRepository(ctx, rw, rw, kms)
}
iamRepo := iam.TestRepo(t, conn, wrap)
org, _ := iam.TestScopes(t, iamRepo)
at := authtoken.TestAuthToken(t, conn, kms, org.GetPublicId())
s, err := authtokens.NewService(ctx, repoFn, iamRepoFn, 1000)
require.NoError(err, "Error when getting new user service")
req := &pbs.DeleteAuthTokenRequest{
Id: at.GetPublicId(),
}
_, gErr := s.DeleteAuthToken(auth.DisabledAuthTestContext(iamRepoFn, at.GetScopeId()), req)
assert.NoError(gErr, "First attempt")
_, gErr = s.DeleteAuthToken(auth.DisabledAuthTestContext(iamRepoFn, at.GetScopeId()), req)
assert.Error(gErr, "Second attempt")
assert.True(errors.Is(gErr, handlers.ApiErrorWithCode(codes.NotFound)), "Expected permission denied for the second delete.")
}