Files
threshold/internal/authtoken/repository_test.go
T
hashicorp-copywrite[bot]andJohan Brandhorst-Satzkorn 29da0bcb92 [COMPLIANCE] License changes (#3567)
* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Adding explicit MPL license for sub-package

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Updating the license from MPL to Business Source License

Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl.

* Update copyright file headers to BUS-1.1

* Rerun make gen

This will pick up the last of the license changes

* Revert "Rerun make gen"

This reverts commit dbe0968bffd92745d8663c6940debae098395a6b.

* Remove copywrite from generation script

The current version of copywrite is not sophisticated enough
to only apply the copywrite changes to certain subdirectories,
so to avoid accidentally relicensing our api and sdk modules,
we will stop running the copywrite script until such time
that it can do the right thing.

* License protobuf API as MPL

The protobuf API files are used to generate the SDK,
since we want the SDK to be MPL, the protobuf files
must also be MPL. This extends to all protobuf files
whose sources end up in the SDK directory, including:

- internal/proto/controller/api
- internal/proto/controller/custom_options
- internal/proto/plugin

It also includes the generated service directory in
internal/gen/controller/api/services, since they are
generated from the protobuf sources used for the SDK.

* Change website license file name

This makes the naming scheme consistent with the rest
of the repository.

* Add note on licensing to README

* Set copywrite license to BUSL

* Revert "Add note on licensing to README"

This reverts commit 4d865a91a7a4f11605f0dc4609355d7e3c3cfffe.

---------

Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
Co-authored-by: Johan Brandhorst-Satzkorn <johan.brandhorst@gmail.com>
2023-08-11 10:31:53 -07:00

883 lines
24 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package authtoken
import (
"context"
"sort"
"testing"
"time"
"github.com/golang/protobuf/ptypes"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/auth/password"
"github.com/hashicorp/boundary/internal/db/timestamp"
"github.com/hashicorp/boundary/internal/kms"
"github.com/hashicorp/boundary/internal/oplog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/testing/protocmp"
"github.com/hashicorp/boundary/internal/authtoken/store"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
)
func TestRepository_New(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kmsCache := kms.TestKms(t, conn, wrapper)
type args struct {
r db.Reader
w db.Writer
kms *kms.Kms
opts []Option
}
tests := []struct {
name string
args args
want *Repository
wantIsErr errors.Code
wantErrMsg string
}{
{
name: "valid default limit",
args: args{
r: rw,
w: rw,
kms: kmsCache,
opts: []Option{},
},
want: &Repository{
reader: rw,
writer: rw,
kms: kmsCache,
limit: db.DefaultLimit,
timeToLiveDuration: defaultTokenTimeToLiveDuration,
timeToStaleDuration: defaultTokenTimeToStaleDuration,
},
},
{
name: "valid new limit",
args: args{
r: rw,
w: rw,
kms: kmsCache,
opts: []Option{
WithLimit(5),
},
},
want: &Repository{
reader: rw,
writer: rw,
kms: kmsCache,
limit: 5,
timeToLiveDuration: defaultTokenTimeToLiveDuration,
timeToStaleDuration: defaultTokenTimeToStaleDuration,
},
},
{
name: "valid token time to live",
args: args{
r: rw,
w: rw,
kms: kmsCache,
opts: []Option{
WithTokenTimeToLiveDuration(1 * time.Hour),
},
},
want: &Repository{
reader: rw,
writer: rw,
kms: kmsCache,
limit: db.DefaultLimit,
timeToLiveDuration: 1 * time.Hour,
timeToStaleDuration: defaultTokenTimeToStaleDuration,
},
},
{
name: "valid token time to stale",
args: args{
r: rw,
w: rw,
kms: kmsCache,
opts: []Option{
WithTokenTimeToStaleDuration(1 * time.Hour),
},
},
want: &Repository{
reader: rw,
writer: rw,
kms: kmsCache,
limit: db.DefaultLimit,
timeToStaleDuration: 1 * time.Hour,
timeToLiveDuration: defaultTokenTimeToLiveDuration,
},
},
{
name: "nil-reader",
args: args{
r: nil,
w: rw,
kms: kmsCache,
},
want: nil,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "authtoken.NewRepository: nil db reader: parameter violation: error #100",
},
{
name: "nil-writer",
args: args{
r: rw,
w: nil,
kms: kmsCache,
},
want: nil,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "authtoken.NewRepository: nil db writer: parameter violation: error #100",
},
{
name: "nil-kms",
args: args{
r: rw,
w: rw,
kms: nil,
},
want: nil,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "authtoken.NewRepository: nil kms: parameter violation: error #100",
},
{
name: "all-nils",
args: args{
r: nil,
w: nil,
kms: nil,
},
want: nil,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "authtoken.NewRepository: nil db reader: parameter violation: error #100",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert := assert.New(t)
got, err := NewRepository(context.Background(), tt.args.r, tt.args.w, tt.args.kms, tt.args.opts...)
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "Unexpected error %s", err)
assert.Equal(tt.wantErrMsg, err.Error())
return
}
assert.NoError(err)
assert.NotNil(got)
assert.Equal(tt.want, got)
})
}
}
func TestRepository_CreateAuthToken(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
repo := iam.TestRepo(t, conn, wrapper)
org1, _ := iam.TestScopes(t, repo)
am := password.TestAuthMethods(t, conn, org1.GetPublicId(), 1)[0]
aAcct := password.TestAccount(t, conn, am.GetPublicId(), "name1")
iam.TestSetPrimaryAuthMethod(t, repo, org1, am.PublicId)
u1 := iam.TestUser(t, repo, org1.PublicId, iam.WithAccountIds(aAcct.PublicId))
org2, _ := iam.TestScopes(t, repo)
u2 := iam.TestUser(t, repo, org2.GetPublicId())
testId, err := NewAuthTokenId(ctx)
require.NoError(t, err)
tests := []struct {
name string
iamUser *iam.User
authAcctId string
opt []Option
want *AuthToken
wantErr bool
}{
{
name: "valid",
iamUser: u1,
authAcctId: aAcct.GetPublicId(),
want: &AuthToken{
AuthToken: &store.AuthToken{
AuthAccountId: aAcct.GetPublicId(),
},
},
},
{
name: "WithPublicId-WithStatus",
iamUser: u1,
authAcctId: aAcct.GetPublicId(),
opt: []Option{WithPublicId(testId), WithStatus(PendingStatus)},
want: &AuthToken{
AuthToken: &store.AuthToken{
AuthAccountId: aAcct.GetPublicId(),
PublicId: testId,
Status: string(PendingStatus),
},
},
},
{
name: "unconnected-authaccount-user",
iamUser: u2,
authAcctId: aAcct.GetPublicId(),
wantErr: true,
},
{
name: "no-authacctid",
iamUser: u1,
wantErr: true,
},
{
name: "no-userid",
authAcctId: aAcct.GetPublicId(),
wantErr: true,
},
{
name: "invalid-authacctid",
iamUser: u1,
authAcctId: "this_is_invalid",
wantErr: true,
},
{
name: "invalid-userid",
iamUser: func() *iam.User { u := u1.Clone().(*iam.User); u.PublicId = "this_is_invalid"; return u }(),
authAcctId: aAcct.GetPublicId(),
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
repo, err := NewRepository(ctx, rw, rw, kms)
require.NoError(err)
require.NotNil(repo)
got, err := repo.CreateAuthToken(ctx, tt.iamUser, tt.authAcctId, tt.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(got)
return
}
require.NoError(err, "Got error for CreateAuthToken(ctx, %v, %v)", tt.iamUser, tt.authAcctId)
assert.NotNil(got)
db.AssertPublicId(t, globals.AuthTokenPrefix, got.PublicId)
assert.Equal(tt.authAcctId, got.GetAuthAccountId())
assert.Equal(got.CreateTime, got.UpdateTime)
assert.Equal(got.CreateTime, got.ApproximateLastAccessTime)
opts := getOpts(tt.opt...)
if opts.withPublicId != "" {
assert.Equal(opts.withPublicId, got.PublicId)
}
if opts.withStatus != "" {
assert.Equal(string(opts.withStatus), got.Status)
}
// We should find no oplog since tokens are not replicated, so they don't need oplog entries.
assert.Error(db.TestVerifyOplog(t, rw, got.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_CREATE)))
})
}
}
func TestRepository_LookupAuthToken(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
repo := iam.TestRepo(t, conn, wrapper)
org, _ := iam.TestScopes(t, repo)
at := TestAuthToken(t, conn, kms, org.GetPublicId())
at.Token = ""
at.CtToken = nil
at.KeyId = ""
badId, err := NewAuthTokenId(ctx)
require.NoError(t, err)
require.NotNil(t, badId)
tests := []struct {
name string
id string
want *AuthToken
wantIsErr errors.Code
wantErrMsg string
}{
{
name: "found",
id: at.GetPublicId(),
want: at,
},
{
name: "not-found",
id: badId,
want: nil,
},
{
name: "bad-public-id",
id: "",
want: nil,
wantIsErr: errors.InvalidPublicId,
wantErrMsg: "authtoken.(Repository).LookupAuthToken: missing public id: parameter violation: error #102",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
repo, err := NewRepository(ctx, rw, rw, kms)
require.NoError(err)
require.NotNil(repo)
got, err := repo.LookupAuthToken(ctx, tt.id)
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "Unexpected error %s", err)
assert.Equal(tt.wantErrMsg, err.Error())
return
}
assert.NoError(err)
if got == nil {
assert.Nil(tt.want)
return
}
require.NotNil(tt.want, "got %v, wanted nil", got)
// TODO: This test fails by a very small amount -- 500 nanos ish in
// my experience -- if they are required to be equal. I think this
// is because the resolution of the timestamp in the db does not
// match the resolution in Go code. But might be worth checking
// into.
wantGoTimeExpr, err := ptypes.Timestamp(tt.want.AuthToken.GetExpirationTime().Timestamp)
require.NoError(err)
gotGoTimeExpr, err := ptypes.Timestamp(got.AuthToken.GetExpirationTime().Timestamp)
require.NoError(err)
assert.WithinDuration(wantGoTimeExpr, gotGoTimeExpr, time.Millisecond)
tt.want.AuthToken.ExpirationTime = got.AuthToken.ExpirationTime
assert.Empty(cmp.Diff(tt.want.AuthToken, got.AuthToken, protocmp.Transform()))
})
}
}
func TestRepository_ValidateToken(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
lastAccessedUpdateDuration = 0
timeSkew = 20 * time.Millisecond
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
iamRepo := iam.TestRepo(t, conn, wrapper)
repo, err := NewRepository(ctx, rw, rw, kms)
require.NoError(t, err)
require.NotNil(t, repo)
org, _ := iam.TestScopes(t, iamRepo)
at := TestAuthToken(t, conn, kms, org.GetPublicId())
atToken := at.GetToken()
at.Token = ""
at.CtToken = nil
at.KeyId = ""
atTime, err := ptypes.Timestamp(at.GetApproximateLastAccessTime().GetTimestamp())
require.NoError(t, err)
require.NotNil(t, atTime)
badId, err := NewAuthTokenId(ctx)
require.NoError(t, err)
require.NotNil(t, badId)
badToken, err := newAuthToken(ctx)
require.NoError(t, err)
require.NotNil(t, badToken)
tests := []struct {
name string
id string
token string
want *AuthToken
wantIsErr errors.Code
wantErrMsg string
}{
{
name: "exists",
id: at.GetPublicId(),
token: atToken,
want: at,
},
{
name: "doesnt-exist",
id: badId,
token: badToken.Token,
want: nil,
},
{
name: "empty-token",
id: at.GetPublicId(),
token: "",
want: nil,
wantIsErr: errors.InvalidParameter,
wantErrMsg: "authtoken.(Repository).ValidateToken: missing token: parameter violation: error #100",
},
{
name: "mismatched-token",
id: at.GetPublicId(),
token: badToken.Token,
want: nil,
wantIsErr: errors.Unknown,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, err := repo.ValidateToken(ctx, tt.id, tt.token)
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "Unexpected error %s", err)
assert.Equal(tt.wantErrMsg, err.Error())
return
}
assert.NoError(err)
if got == nil {
assert.Nil(tt.want)
// No need to compare updated time if we didn't get an initial auth token to compare against.
return
}
require.NotNil(tt.want, "Got %v but wanted nil", got)
// NOTE: See comment in LookupAuthToken about this logic
wantGoTimeExpr, err := ptypes.Timestamp(tt.want.AuthToken.GetExpirationTime().Timestamp)
require.NoError(err)
gotGoTimeExpr, err := ptypes.Timestamp(got.AuthToken.GetExpirationTime().Timestamp)
require.NoError(err)
assert.WithinDuration(wantGoTimeExpr, gotGoTimeExpr, time.Millisecond)
tt.want.AuthToken.ExpirationTime = got.AuthToken.ExpirationTime
assert.Empty(cmp.Diff(tt.want.AuthToken, got.AuthToken, protocmp.Transform()))
// preTime1 should be the value prior to the ValidateToken was called so it should equal creation time
preTime1, err := ptypes.Timestamp(got.GetApproximateLastAccessTime().GetTimestamp())
require.NoError(err)
assert.True(preTime1.Equal(atTime), "Create time %q doesn't match the time from the first call to MaybeUpdateLastAccesssed: %q.", atTime, preTime1)
// Enable the duration which limits how frequently a token's approximate last accessed time can be updated
// so the next call doesn't cause the last accessed time to be updated.
lastAccessedUpdateDuration = 1 * time.Hour
got2, err := repo.ValidateToken(ctx, tt.id, tt.token)
assert.NoError(err)
preTime2, err := ptypes.Timestamp(got2.GetApproximateLastAccessTime().GetTimestamp())
require.NoError(err)
assert.True(preTime2.After(preTime1), "First updated time %q was not after the creation time %q", preTime2, preTime1)
// We should find no oplog since tokens are not replicated, so they don't need oplog entries.
assert.Error(db.TestVerifyOplog(t, rw, got.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_UPDATE)))
got3, err := repo.ValidateToken(ctx, tt.id, tt.token)
require.NoError(err)
preTime3, err := ptypes.Timestamp(got3.GetApproximateLastAccessTime().GetTimestamp())
require.NoError(err)
assert.True(preTime3.Equal(preTime2), "The 3rd timestamp %q was not equal to the second time %q", preTime3, preTime2)
})
}
}
func TestRepository_ValidateToken_expired(t *testing.T) {
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
iamRepo := iam.TestRepo(t, conn, wrapper)
org, _ := iam.TestScopes(t, iamRepo)
baseAT := TestAuthToken(t, conn, kms, org.GetPublicId())
baseAT.GetAuthAccountId()
aAcct := allocAuthAccount()
aAcct.PublicId = baseAT.GetAuthAccountId()
require.NoError(t, rw.LookupByPublicId(context.Background(), aAcct))
iamUser, _, err := iamRepo.LookupUser(context.Background(), aAcct.GetIamUserId())
require.NoError(t, err)
require.NotNil(t, iamUser)
tests := []struct {
name string
staleDuration time.Duration
expirationDuration time.Duration
wantReturned bool
}{
{
name: "not-stale-or-expired",
staleDuration: defaultTokenTimeToStaleDuration,
expirationDuration: defaultTokenTimeToLiveDuration,
wantReturned: true,
},
{
name: "stale",
staleDuration: 1 * time.Millisecond,
expirationDuration: defaultTokenTimeToLiveDuration,
wantReturned: false,
},
{
name: "expired",
staleDuration: defaultTokenTimeToStaleDuration,
expirationDuration: 1 * time.Millisecond,
wantReturned: false,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
ctx := context.Background()
timeSkew = 20 * time.Millisecond
repo, err := NewRepository(ctx, rw, rw, kms,
WithTokenTimeToLiveDuration(tt.expirationDuration),
WithTokenTimeToStaleDuration(tt.staleDuration))
require.NoError(err)
require.NotNil(repo)
at, err := repo.CreateAuthToken(ctx, iamUser, baseAT.GetAuthAccountId())
require.NoError(err)
got, err := repo.ValidateToken(ctx, at.GetPublicId(), at.GetToken())
require.NoError(err)
if tt.wantReturned {
assert.NotNil(got)
} else {
// We should find no oplog since tokens are not replicated, so they don't need oplog entries.
assert.Error(db.TestVerifyOplog(t, rw, at.GetPublicId(), db.WithOperation(oplog.OpType_OP_TYPE_DELETE)))
assert.Nil(got)
}
})
}
}
func TestRepository_DeleteAuthToken(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
repo := iam.TestRepo(t, conn, wrapper)
org, _ := iam.TestScopes(t, repo)
at := TestAuthToken(t, conn, kms, org.GetPublicId())
badId, err := NewAuthTokenId(ctx)
require.NoError(t, err)
require.NotNil(t, badId)
tests := []struct {
name string
id string
want int
wantIsErr errors.Code
wantErrMsg string
}{
{
name: "found",
id: at.GetPublicId(),
want: 1,
},
{
name: "not-found",
id: badId,
want: 0,
},
{
name: "empty-public-id",
id: "",
want: 0,
wantIsErr: errors.InvalidPublicId,
wantErrMsg: "authtoken.(Repository).DeleteAuthToken: missing public id: parameter violation: error #102",
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
repo, err := NewRepository(ctx, rw, rw, kms)
require.NoError(err)
require.NotNil(repo)
got, err := repo.DeleteAuthToken(ctx, tt.id)
if tt.wantIsErr != 0 {
assert.Truef(errors.Match(errors.T(tt.wantIsErr), err), "Unexpected error %s", err)
assert.Equal(tt.wantErrMsg, err.Error())
return
}
assert.NoError(err)
assert.Equal(tt.want, got, "row count")
if tt.want != 0 {
// We should find no oplog since tokens are not replicated, so they don't need oplog entries.
assert.Error(db.TestVerifyOplog(t, rw, tt.id, db.WithOperation(oplog.OpType_OP_TYPE_DELETE)))
}
})
}
}
func TestRepository_ListAuthTokens(t *testing.T) {
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
repo := iam.TestRepo(t, conn, wrapper)
org, _ := iam.TestScopes(t, repo)
at1 := TestAuthToken(t, conn, kms, org.GetPublicId())
at1.Token = ""
at1.KeyId = ""
at2 := TestAuthToken(t, conn, kms, org.GetPublicId())
at2.Token = ""
at2.KeyId = ""
at3 := TestAuthToken(t, conn, kms, org.GetPublicId())
at3.Token = ""
at3.KeyId = ""
emptyOrg, _ := iam.TestScopes(t, repo)
tests := []struct {
name string
orgId string
want []*AuthToken
}{
{
name: "populated",
orgId: org.GetPublicId(),
want: []*AuthToken{at1, at2, at3},
},
{
name: "empty",
orgId: emptyOrg.GetPublicId(),
want: []*AuthToken{},
},
{
name: "empty-org-id",
orgId: "",
want: []*AuthToken{},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
repo, err := NewRepository(ctx, rw, rw, kms)
require.NoError(err)
require.NotNil(repo)
got, err := repo.ListAuthTokens(ctx, []string{tt.orgId})
assert.NoError(err)
sort.Slice(tt.want, func(i, j int) bool { return tt.want[i].PublicId < tt.want[j].PublicId })
sort.Slice(got, func(i, j int) bool { return got[i].PublicId < got[j].PublicId })
assert.Empty(cmp.Diff(tt.want, got, protocmp.Transform()), "row count")
})
}
}
func TestRepository_ListAuthTokens_Multiple_Scopes(t *testing.T) {
t.Parallel()
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
kms := kms.TestKms(t, conn, wrapper)
repo, err := NewRepository(ctx, rw, rw, kms)
require.NoError(t, err)
iamRepo := iam.TestRepo(t, conn, wrapper)
org, proj := iam.TestScopes(t, iamRepo)
const numPerScope = 10
var total int
for i := 0; i < numPerScope; i++ {
TestAuthToken(t, conn, kms, "global")
total++
TestAuthToken(t, conn, kms, org.GetPublicId())
total++
}
got, err := repo.ListAuthTokens(ctx, []string{"global", org.GetPublicId(), proj.GetPublicId()})
require.NoError(t, err)
assert.Equal(t, total, len(got))
}
func Test_IssuePendingToken(t *testing.T) {
t.Parallel()
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
rootWrapper := db.TestWrapper(t)
kmsCache := kms.TestKms(t, conn, rootWrapper)
repo, err := NewRepository(ctx, rw, rw, kmsCache)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, rootWrapper))
tests := []struct {
name string
tokenRequestId string
wantErrMatch *errors.Template
wantErrContains string
}{
{
name: "missing-id",
wantErrMatch: errors.T(errors.InvalidParameter),
wantErrContains: "missing token request id",
},
{
name: "not-found",
tokenRequestId: func() string {
tokenPublicId, err := NewAuthTokenId(ctx)
require.NoError(t, err)
tk := TestAuthToken(t, conn, kmsCache, org.PublicId, WithPublicId(tokenPublicId))
return tk.PublicId
}(),
wantErrMatch: errors.T(errors.RecordNotFound),
wantErrContains: "pending auth token",
},
{
name: "success",
tokenRequestId: func() string {
tokenPublicId, err := NewAuthTokenId(ctx)
require.NoError(t, err)
tk := TestAuthToken(t, conn, kmsCache, org.PublicId, WithStatus(PendingStatus), WithPublicId(tokenPublicId))
return tk.PublicId
}(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
tk, err := repo.IssueAuthToken(ctx, tt.tokenRequestId)
if tt.wantErrMatch != nil {
require.Error(err)
assert.Truef(errors.Match(tt.wantErrMatch, err), "wanted %s and got: %+v", tt.wantErrMatch.Code, err)
if tt.wantErrContains != "" {
assert.Contains(err.Error(), tt.wantErrContains)
}
return
}
require.NoError(err)
assert.NotEmpty(tk)
accessTime, err := ptypes.Timestamp(tk.GetApproximateLastAccessTime().GetTimestamp())
require.NoError(err)
createTime, err := ptypes.Timestamp(tk.GetCreateTime().GetTimestamp())
require.NoError(err)
assert.True(accessTime.After(createTime), "last access time %q was not after the creation time %q", accessTime, createTime)
})
}
}
func Test_CloseExpiredPendingTokens(t *testing.T) {
t.Parallel()
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
rootWrapper := db.TestWrapper(t)
kmsCache := kms.TestKms(t, conn, rootWrapper)
repo, err := NewRepository(ctx, rw, rw, kmsCache)
require.NoError(t, err)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, rootWrapper))
databaseWrapper, err := kmsCache.GetWrapper(ctx, org.PublicId, kms.KeyPurposeDatabase)
require.NoError(t, err)
createWith := func(cnt int, expIn time.Duration, status Status) {
authMethods := password.TestAuthMethods(t, conn, org.PublicId, 1)
authMethodId := authMethods[0].PublicId
accts := password.TestMultipleAccounts(t, conn, authMethodId, cnt)
for i := 0; i < cnt; i++ {
at := allocAuthToken()
id, err := NewAuthTokenId(ctx)
require.NoError(t, err)
at.PublicId = id
exp, err := ptypes.TimestampProto(time.Now().Add(expIn).Truncate(time.Second))
require.NoError(t, err)
at.ExpirationTime = &timestamp.Timestamp{Timestamp: exp}
at.Status = string(status)
at.AuthAccountId = accts[i].PublicId
keyId, err := databaseWrapper.KeyId(ctx)
require.NoError(t, err)
at.KeyId = keyId
at.CtToken = []byte(id)
err = rw.Create(ctx, at)
require.NoError(t, err)
}
}
tests := []struct {
name string
setup func()
wantCnt int
wantErrMatch *errors.Template
wantErrContains string
}{
{
name: "nada-todo",
},
{
name: "close-2",
setup: func() {
createWith(2, -10*time.Second, PendingStatus)
createWith(1, 10*time.Second, IssuedStatus)
createWith(1, 10*time.Second, PendingStatus)
},
wantCnt: 2,
},
{
name: "close-zero",
setup: func() {
createWith(2, 10*time.Second, PendingStatus)
createWith(1, 10*time.Second, IssuedStatus)
},
wantCnt: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
// start with no tokens in the db.
_, err := rw.Exec(ctx, "delete from auth_token", nil)
require.NoError(err)
_, err = rw.Exec(ctx, "delete from auth_account", nil)
require.NoError(err)
if tt.setup != nil {
tt.setup()
}
tokensClosed, err := repo.CloseExpiredPendingTokens(ctx)
if tt.wantErrMatch != nil {
require.Error(err)
assert.Equal(0, tokensClosed)
assert.Truef(errors.Match(tt.wantErrMatch, err), "wanted %s and got: %s", tt.wantErrMatch.Code, err.Error())
if tt.wantErrContains != "" {
assert.Contains(err.Error(), tt.wantErrContains)
}
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, tokensClosed)
})
}
}