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/auth/oidc/account_test.go

378 lines
12 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package oidc
import (
"context"
"net/url"
"strings"
"testing"
"github.com/hashicorp/boundary/internal/db"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam"
"github.com/hashicorp/boundary/internal/kms"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestAccount_Create(t *testing.T) {
t.Parallel()
ctx := context.TODO()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
kmsCache := kms.TestKms(t, conn, wrapper)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
rw := db.New(conn)
databaseWrapper, err := kmsCache.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase)
require.NoError(t, err)
testAuthMethod := TestAuthMethod(t, conn, databaseWrapper, org.PublicId, InactiveState, "alice_rp",
"my-dogs-name", WithIssuer(TestConvertToUrls(t, "https://alice.com")[0]), WithApiUrl(TestConvertToUrls(t, "https://api.com")[0]))
type args struct {
authMethodId string
subject string
opts []Option
}
tests := []struct {
name string
args args
want *Account
wantErr bool
wantIsErr errors.Code
create bool
wantCreateErr bool
wantCreateIsErr errors.Code
}{
{
name: "valid",
args: args{
authMethodId: testAuthMethod.PublicId,
subject: "alice",
opts: []Option{WithIssuer(TestConvertToUrls(t, "https://alice.com")[0]), WithEmail("alice@alice.com"), WithFullName("Alice Eve Smith"), WithName("alice's restaurant"), WithDescription("A good place to eat")},
},
create: true,
want: func() *Account {
want, err := NewAccount(ctx, testAuthMethod.PublicId, "alice", WithIssuer(TestConvertToUrls(t, "https://alice.com")[0]), WithEmail("alice@alice.com"), WithFullName("Alice Eve Smith"), WithName("alice's restaurant"), WithDescription("A good place to eat"))
require.NoError(t, err)
return want
}(),
},
{
name: "dup", // must follow "valid" test.
args: args{
authMethodId: testAuthMethod.PublicId,
subject: "alice",
opts: []Option{WithIssuer(TestConvertToUrls(t, "https://alice.com")[0]), WithEmail("alice@alice.com"), WithFullName("Alice Eve Smith"), WithName("alice's restaurant"), WithDescription("A good place to eat")},
},
create: true,
want: func() *Account {
want, err := NewAccount(ctx, testAuthMethod.PublicId, "alice", WithIssuer(TestConvertToUrls(t, "https://alice.com")[0]), WithEmail("alice@alice.com"), WithFullName("Alice Eve Smith"), WithName("alice's restaurant"), WithDescription("A good place to eat"))
require.NoError(t, err)
return want
}(),
wantCreateErr: true,
wantCreateIsErr: errors.NotUnique,
},
{
name: "mismatch issuer",
args: args{
authMethodId: testAuthMethod.PublicId,
subject: "newsubject",
opts: []Option{WithIssuer(TestConvertToUrls(t, "https://somethingelse.com")[0])},
},
create: true,
want: func() *Account {
want, err := NewAccount(ctx, testAuthMethod.PublicId, "newsubject", WithIssuer(TestConvertToUrls(t, "https://somethingelse.com")[0]))
require.NoError(t, err)
return want
}(),
},
{
name: "empty-auth-method",
args: args{
authMethodId: "",
subject: "alice",
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-subject",
args: args{
authMethodId: testAuthMethod.PublicId,
subject: "",
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "email-too-long",
args: args{
authMethodId: testAuthMethod.PublicId,
subject: "alice",
opts: []Option{WithEmail(strings.Repeat("a", 500) + "@alice.com")},
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "name-too-long",
args: args{
authMethodId: testAuthMethod.PublicId,
subject: "alice",
opts: []Option{WithFullName(strings.Repeat("a", 750))},
},
wantErr: true,
wantIsErr: errors.InvalidParameter,
},
{
name: "empty-issuer",
args: args{
authMethodId: testAuthMethod.PublicId,
subject: "alice",
opts: []Option{WithIssuer(&url.URL{})},
},
create: true,
want: func() *Account {
want, err := NewAccount(ctx, testAuthMethod.PublicId, "alice", WithIssuer(&url.URL{}))
require.NoError(t, err)
return want
}(),
wantCreateErr: true,
wantCreateIsErr: errors.CheckConstraint,
},
{
name: "nil-issuer",
args: args{
authMethodId: testAuthMethod.PublicId,
subject: "alice",
},
create: true,
want: func() *Account {
want, err := NewAccount(ctx, testAuthMethod.PublicId, "alice")
require.NoError(t, err)
return want
}(),
wantCreateErr: true,
wantCreateIsErr: errors.CheckConstraint,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
got, err := NewAccount(ctx, tt.args.authMethodId, tt.args.subject, tt.args.opts...)
if tt.wantErr {
require.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsErr), err))
return
}
require.NoError(err)
assert.Equal(tt.want, got)
if tt.create {
ctx := context.Background()
id, err := newAccountId(ctx, testAuthMethod.GetPublicId(), testAuthMethod.GetIssuer(), tt.args.subject)
require.NoError(err)
got.PublicId = id
err = rw.Create(ctx, got)
if tt.wantCreateErr {
assert.Error(err)
assert.True(errors.Match(errors.T(tt.wantCreateIsErr), err), err)
return
} else {
assert.NoError(err)
}
found := AllocAccount()
found.PublicId = id
err = rw.LookupByPublicId(ctx, found)
require.NoError(err)
assert.Equal(got, found)
}
})
}
t.Run("account issuer stays when auth method discovery url changes", func(t *testing.T) {
am := TestAuthMethod(t, conn, databaseWrapper, org.PublicId, InactiveState, "client", "secret",
WithIssuer(TestConvertToUrls(t, "https://discovery.com")[0]), WithApiUrl(TestConvertToUrls(t, "https://api.com")[0]))
a, err := NewAccount(ctx, am.GetPublicId(), "subject", WithIssuer(TestConvertToUrls(t, am.GetIssuer())[0]))
require.NoError(t, err)
id, err := newAccountId(ctx, am.GetPublicId(), am.GetIssuer(), a.GetSubject())
require.NoError(t, err)
a.PublicId = id
ctx := context.Background()
require.NoError(t, rw.Create(ctx, a))
assert.Equal(t, am.GetIssuer(), a.GetIssuer())
discoUrl := am.GetIssuer()
newDiscoUrl := "https://changed.com"
am.Issuer = newDiscoUrl
n, err := rw.Update(ctx, am, []string{IssuerField}, nil)
require.NoError(t, err)
assert.Equal(t, 1, n)
assert.Equal(t, newDiscoUrl, am.GetIssuer())
require.NoError(t, rw.LookupById(ctx, a))
assert.Equal(t, discoUrl, a.GetIssuer())
})
}
func TestAccount_Delete(t *testing.T) {
t.Parallel()
ctx := context.TODO()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
kmsCache := kms.TestKms(t, conn, wrapper)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
rw := db.New(conn)
databaseWrapper, err := kmsCache.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase)
require.NoError(t, err)
testAuthMethod := TestAuthMethod(
t,
conn,
databaseWrapper,
org.PublicId,
InactiveState,
"alice_rp",
"my-dogs-name",
WithApiUrl(TestConvertToUrls(t, "https://api.com")[0]),
WithIssuer(TestConvertToUrls(t, "https://alice.com")[0]))
testResource := func(authMethodId string, subject string) *Account {
u, err := url.Parse(testAuthMethod.GetIssuer())
require.NoError(t, err)
a, err := NewAccount(ctx, authMethodId, subject, WithIssuer(u))
require.NoError(t, err)
id, err := newAccountId(ctx, testAuthMethod.GetPublicId(), testAuthMethod.GetIssuer(), subject)
require.NoError(t, err)
a.PublicId = id
return a
}
// seed an extra callback url to just make sure the delete only gets the right num of rows
seedAccount := testResource(testAuthMethod.PublicId, "jane")
require.NoError(t, rw.Create(context.Background(), &seedAccount))
tests := []struct {
name string
Account *Account
wantRowsDeleted int
overrides func(*Account)
wantErr bool
wantErrMsg string
}{
{
name: "valid",
Account: testResource(testAuthMethod.PublicId, "alice"),
wantErr: false,
wantRowsDeleted: 1,
},
{
name: "bad-publicId",
Account: testResource(testAuthMethod.PublicId, "bad-publicId"),
overrides: func(c *Account) { c.PublicId = "bad-id" },
wantErr: false,
wantRowsDeleted: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
ctx := context.Background()
cp := tt.Account.Clone()
require.NoError(rw.Create(ctx, &cp))
if tt.overrides != nil {
tt.overrides(cp)
}
deletedRows, err := rw.Delete(ctx, &cp)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
if tt.wantRowsDeleted == 0 {
assert.Equal(tt.wantRowsDeleted, deletedRows)
return
}
assert.Equal(tt.wantRowsDeleted, deletedRows)
found := AllocAccount()
found.PublicId = tt.Account.PublicId
err = rw.LookupByPublicId(ctx, found)
assert.True(errors.IsNotFoundError(err))
})
}
}
func TestAccount_Clone(t *testing.T) {
t.Parallel()
ctx := context.TODO()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
kmsCache := kms.TestKms(t, conn, wrapper)
t.Run("valid", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
databaseWrapper, err := kmsCache.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase)
require.NoError(err)
m := TestAuthMethod(t, conn, databaseWrapper, org.PublicId, InactiveState, "alice_rp", "my-dogs-name",
WithIssuer(TestConvertToUrls(t, "https://alice.com")[0]), WithApiUrl(TestConvertToUrls(t, "https://api.com")[0]))
orig, err := NewAccount(ctx, m.PublicId, "alice", WithFullName("Alice Eve Smith"), WithEmail("alice@alice.com"))
require.NoError(err)
cp := orig.Clone()
assert.True(proto.Equal(cp.Account, orig.Account))
})
t.Run("not-equal", func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
org, _ := iam.TestScopes(t, iam.TestRepo(t, conn, wrapper))
databaseWrapper, err := kmsCache.GetWrapper(context.Background(), org.PublicId, kms.KeyPurposeDatabase)
require.NoError(err)
m := TestAuthMethod(t, conn, databaseWrapper, org.PublicId, InactiveState, "alice_rp", "my-dogs-name",
WithIssuer(TestConvertToUrls(t, "https://alice.com")[0]), WithApiUrl(TestConvertToUrls(t, "https://api.com")[0]))
orig, err := NewAccount(ctx, m.PublicId, "alice", WithFullName("Alice Eve Smith"), WithEmail("alice@alice.com"))
require.NoError(err)
orig2, err := NewAccount(ctx, m.PublicId, "bob", WithFullName("Bob Eve Smith"), WithEmail("bob@alice.com"))
require.NoError(err)
cp := orig.Clone()
assert.True(!proto.Equal(cp.Account, orig2.Account))
})
}
func TestAccount_SetTableName(t *testing.T) {
t.Parallel()
defaultTableName := defaultAccountTableName
tests := []struct {
name string
setNameTo string
want string
}{
{
name: "new-name",
setNameTo: "new-name",
want: "new-name",
},
{
name: "reset to default",
setNameTo: "",
want: defaultTableName,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
def := AllocAccount()
require.Equal(defaultTableName, def.TableName())
m := AllocAccount()
m.SetTableName(tt.setNameTo)
assert.Equal(tt.want, m.TableName())
})
}
}