Files
threshold/internal/tests/api/users/user_test.go
T
Jeff Mitchell 62d4f8453b Don't explode grants from the DB (#5104)
This removes the Cartesian product in the DB for GrantsForUser in favor
of returning the actual grant scope information and dealing with it in
application code.

(cherry picked from commit 913cd536823d73994acc4f4d833208c1cc05f91b)
2024-09-24 14:18:53 +00:00

319 lines
11 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package users_test
import (
"fmt"
"net/http"
"testing"
"github.com/hashicorp/boundary/api"
"github.com/hashicorp/boundary/api/accounts"
"github.com/hashicorp/boundary/api/aliases"
"github.com/hashicorp/boundary/api/authmethods"
"github.com/hashicorp/boundary/api/roles"
"github.com/hashicorp/boundary/api/targets"
"github.com/hashicorp/boundary/api/users"
"github.com/hashicorp/boundary/globals"
"github.com/hashicorp/boundary/internal/daemon/controller"
_ "github.com/hashicorp/boundary/internal/daemon/controller/handlers/targets/tcp"
"github.com/hashicorp/boundary/internal/iam"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const global = "global"
func TestAddSetRemoveAccounts(t *testing.T) {
assert, require := assert.New(t), require.New(t)
tc := controller.NewTestController(t, nil)
defer tc.Shutdown()
client := tc.Client()
token := tc.Token()
client.SetToken(token.Token)
uClient := users.NewClient(client)
aClient := accounts.NewClient(client)
amClient := authmethods.NewClient(client)
userAm, err := amClient.Create(tc.Context(), "password", global,
authmethods.WithName("bar"))
require.NoError(err)
usr1, err := uClient.Create(tc.Context(), global)
require.NoError(err)
acct1, err := aClient.Create(tc.Context(), token.AuthMethodId, accounts.WithPasswordAccountLoginName("accountname"))
require.NoError(err)
acct2, err := aClient.Create(tc.Context(), userAm.Item.Id, accounts.WithPasswordAccountLoginName("accountname2"))
require.NoError(err)
addResult, err := uClient.AddAccounts(tc.Context(), usr1.Item.Id, 0, []string{acct1.Item.Id, acct2.Item.Id}, users.WithAutomaticVersioning(true))
assert.NoError(err)
assert.ElementsMatch([]string{acct1.Item.Id, acct2.Item.Id}, addResult.Item.AccountIds)
// Add the account to the default user
_, err = uClient.AddAccounts(tc.Context(), token.UserId, 0, []string{acct1.Item.Id}, users.WithAutomaticVersioning(true))
assert.Error(err)
remResult, err := uClient.RemoveAccounts(tc.Context(), usr1.Item.Id, 0, []string{acct1.Item.Id}, users.WithAutomaticVersioning(true))
assert.NoError(err)
assert.ElementsMatch([]string{acct2.Item.Id}, remResult.Item.AccountIds)
// Cannot remove an account that isn't associated with a user.
_, err = uClient.RemoveAccounts(tc.Context(), usr1.Item.Id, 0, []string{acct1.Item.Id}, users.WithAutomaticVersioning(true))
assert.Error(err)
setResult, err := uClient.SetAccounts(tc.Context(), usr1.Item.Id, 0, []string{acct1.Item.Id, acct2.Item.Id}, users.WithAutomaticVersioning(true))
assert.NoError(err)
assert.ElementsMatch([]string{acct1.Item.Id, acct2.Item.Id}, setResult.Item.AccountIds)
}
func TestList(t *testing.T) {
assert, require := assert.New(t), require.New(t)
tc := controller.NewTestController(t, nil)
defer tc.Shutdown()
client := tc.Client()
token := tc.Token()
client.SetToken(token.Token)
org := iam.TestOrg(t, tc.IamRepo(), iam.WithUserId(token.UserId))
userClient := users.NewClient(client)
ul, err := userClient.List(tc.Context(), org.GetPublicId())
assert.NoError(err)
assert.Empty(ul.Items)
var expected []*users.User
for i := 0; i < 10; i++ {
expected = append(expected, &users.User{Name: fmt.Sprint(i)})
}
ucr, err := userClient.Create(tc.Context(), org.GetPublicId(), users.WithName(expected[0].Name))
assert.NoError(err)
expected[0] = ucr.Item
ul, err = userClient.List(tc.Context(), org.GetPublicId())
assert.NoError(err)
assert.ElementsMatch(comparableSlice(expected[:1]), comparableSlice(ul.Items))
for i := 1; i < 10; i++ {
ucr, err = userClient.Create(tc.Context(), org.GetPublicId(), users.WithName(expected[i].Name))
assert.NoError(err)
expected[i] = ucr.Item
}
ul, err = userClient.List(tc.Context(), org.GetPublicId())
require.NoError(err)
assert.ElementsMatch(comparableSlice(expected), comparableSlice(ul.Items))
filterItem := ul.Items[3]
ul, err = userClient.List(tc.Context(), org.GetPublicId(),
users.WithFilter(fmt.Sprintf(`"/item/id"==%q`, filterItem.Id)))
require.NoError(err)
assert.Len(ul.Items, 1)
assert.Equal(filterItem.Id, ul.Items[0].Id)
}
func comparableSlice(in []*users.User) []users.User {
var filtered []users.User
for _, i := range in {
p := users.User{
Id: i.Id,
Name: i.Name,
Description: i.Description,
CreatedTime: i.CreatedTime,
UpdatedTime: i.UpdatedTime,
}
filtered = append(filtered, p)
}
return filtered
}
func TestCrud(t *testing.T) {
assert, require := assert.New(t), require.New(t)
tc := controller.NewTestController(t, nil)
defer tc.Shutdown()
client := tc.Client()
token := tc.Token()
client.SetToken(token.Token)
org := iam.TestOrg(t, tc.IamRepo(), iam.WithUserId(token.UserId))
userClient := users.NewClient(client)
checkUser := func(step string, u *users.User, err error, wantedName string, wantedVersion uint32) {
assert.NoError(err, step)
assert.NotNil(u, "returned no resource", step)
gotName := ""
if u.Name != "" {
gotName = u.Name
}
assert.Equal(wantedName, gotName, step)
assert.EqualValues(wantedVersion, u.Version)
}
u, err := userClient.Create(tc.Context(), org.GetPublicId(), users.WithName("foo"))
checkUser("create", u.Item, err, "foo", 1)
u, err = userClient.Read(tc.Context(), u.Item.Id)
checkUser("read", u.Item, err, "foo", 1)
u, err = userClient.Update(tc.Context(), u.Item.Id, u.Item.Version, users.WithName("bar"))
checkUser("update", u.Item, err, "bar", 2)
u, err = userClient.Update(tc.Context(), u.Item.Id, u.Item.Version, users.DefaultName())
checkUser("update", u.Item, err, "", 3)
_, err = userClient.Delete(tc.Context(), u.Item.Id)
require.NoError(err)
_, err = userClient.Delete(tc.Context(), u.Item.Id)
require.Error(err)
apiErr := api.AsServerError(err)
assert.NotNil(apiErr)
assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode())
}
func TestListResolvableAliases(t *testing.T) {
assert, require := assert.New(t), require.New(t)
tc := controller.NewTestController(t, nil)
defer tc.Shutdown()
client := tc.Client()
token := tc.Token()
client.SetToken(token.Token)
{
tarClient := targets.NewClient(client)
resp, err := tarClient.List(tc.Context(), "global", targets.WithRecursive(true))
require.NoError(err)
require.Len(resp.Items, 2)
firstTargetId := resp.Items[0].Id
secondTargetId := resp.Items[1].Id
// Delete the old authenticated user grants
rclient := roles.NewClient(client)
rresp, err := rclient.List(tc.Context(), "global", roles.WithRecursive(true))
require.NoError(err)
require.NotEmpty(t, rresp.Items)
for _, r := range rresp.Items {
if r.Name == "Authenticated User Grants" {
_, err = rclient.Delete(tc.Context(), r.Id)
require.NoError(err)
break
}
}
// add 1 grant to the unauthorized user for only a single target
newR, err := rclient.Create(tc.Context(), "global", roles.WithName("my role"))
require.NoError(err)
_, err = rclient.AddPrincipals(tc.Context(), newR.Item.Id, 0, []string{tc.UnprivilegedToken().UserId}, roles.WithAutomaticVersioning(true))
require.NoError(err)
roleAfterGrants, err := rclient.AddGrants(tc.Context(), newR.Item.Id, 0, []string{
"ids={{.User.Id}};type=user;actions=list-resolvable-aliases",
fmt.Sprintf("ids=%s;type=target;actions=read", firstTargetId),
}, roles.WithAutomaticVersioning(true))
require.NoError(err)
require.NotNil(t, roleAfterGrants)
_, err = rclient.SetGrantScopes(tc.Context(), newR.Item.Id, 0, []string{"this", "descendants"}, roles.WithAutomaticVersioning(true))
require.NoError(err)
aliasClient := aliases.NewClient(client)
aliasResp, err := aliasClient.Create(tc.Context(), "target", "global", aliases.WithValue("second"), aliases.WithDestinationId(secondTargetId))
require.NoError(err)
assert.NotNil(aliasResp)
aliasResp, err = aliasClient.Create(tc.Context(), "target", "global", aliases.WithValue("first"), aliases.WithDestinationId(firstTargetId))
require.NoError(err)
assert.NotNil(aliasResp)
}
// request as admin for admin
userClient := users.NewClient(client)
resp, err := userClient.ListResolvableAliases(tc.Context(), token.UserId)
require.NoError(err)
assert.Len(resp.Items, 2)
assert.Equal("first", resp.Items[0].Value)
assert.NotEmpty(resp.Items[0].DestinationId)
assert.Equal("second", resp.Items[1].Value)
assert.NotEmpty(resp.Items[1].DestinationId)
// request as admin for unprivileged
resp, err = userClient.ListResolvableAliases(tc.Context(), tc.UnprivilegedToken().UserId)
require.NoError(err)
assert.Len(resp.Items, 1)
assert.Equal("first", resp.Items[0].Value)
assert.NotEmpty(resp.Items[0].DestinationId)
// Request as unprivileged for unprivileged
client.SetToken(tc.UnprivilegedToken().Token)
upUserClient := users.NewClient(client)
resp, err = upUserClient.ListResolvableAliases(tc.Context(), tc.UnprivilegedToken().UserId)
require.NoError(err)
assert.Len(resp.Items, 1)
assert.Equal("first", resp.Items[0].Value)
assert.NotEmpty(resp.Items[0].DestinationId)
// Request as unprivileged for admin
_, err = upUserClient.ListResolvableAliases(tc.Context(), tc.Token().UserId)
require.Error(err)
apiErr := api.AsServerError(err)
assert.NotNil(apiErr)
assert.EqualValues(http.StatusForbidden, apiErr.Response().StatusCode())
}
func TestErrors(t *testing.T) {
assert, require := assert.New(t), require.New(t)
tc := controller.NewTestController(t, nil)
defer tc.Shutdown()
client := tc.Client()
token := tc.Token()
client.SetToken(token.Token)
org := iam.TestOrg(t, tc.IamRepo(), iam.WithUserId(token.UserId))
userClient := users.NewClient(client)
u, err := userClient.Create(tc.Context(), org.GetPublicId(), users.WithName("first"))
require.NoError(err)
assert.NotNil(u)
// A malformed id is processed as the id and not a different path to the api.
_, err = userClient.Read(tc.Context(), fmt.Sprintf("%s/../", u.Item.Id))
require.Error(err)
apiErr := api.AsServerError(err)
require.NotNil(apiErr)
assert.EqualValues(http.StatusBadRequest, apiErr.Response().StatusCode())
require.Len(apiErr.Details.RequestFields, 1)
assert.Equal(apiErr.Details.RequestFields[0].Name, "id")
// Updating the wrong version should fail.
_, err = userClient.Update(tc.Context(), u.Item.Id, 73, users.WithName("anything"))
require.Error(err)
apiErr = api.AsServerError(err)
assert.NotNil(apiErr)
assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode())
// Create another resource with the same name.
_, err = userClient.Create(tc.Context(), org.GetPublicId(), users.WithName("first"))
require.Error(err)
apiErr = api.AsServerError(err)
assert.NotNil(apiErr)
_, err = userClient.Read(tc.Context(), globals.UserPrefix+"_doesntexis")
require.Error(err)
apiErr = api.AsServerError(err)
assert.NotNil(apiErr)
assert.EqualValues(http.StatusNotFound, apiErr.Response().StatusCode())
_, err = userClient.Read(tc.Context(), "invalid id")
require.Error(err)
apiErr = api.AsServerError(err)
assert.NotNil(apiErr)
assert.EqualValues(http.StatusBadRequest, apiErr.Response().StatusCode())
_, err = userClient.Update(tc.Context(), u.Item.Id, u.Item.Version)
require.Error(err)
apiErr = api.AsServerError(err)
assert.NotNil(apiErr)
assert.EqualValues(http.StatusBadRequest, apiErr.Response().StatusCode())
}