616 lines
18 KiB
Go
Raw Normal View History

//go:build integration
package user_test
import (
"context"
"fmt"
"slices"
"testing"
"time"
"github.com/brianvoe/gofakeit/v6"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/testing/protocmp"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/integration"
"github.com/zitadel/zitadel/pkg/grpc/filter/v2"
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
)
func TestServer_AddPersonalAccessToken(t *testing.T) {
resp := Instance.CreateUserTypeMachine(IamCTX)
userId := resp.GetId()
expirationDate := timestamppb.New(time.Now().Add(time.Hour * 24))
type args struct {
req *user.AddPersonalAccessTokenRequest
prepare func(request *user.AddPersonalAccessTokenRequest) error
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "add pat, user not existing",
args: args{
&user.AddPersonalAccessTokenRequest{
UserId: "notexisting",
ExpirationDate: expirationDate,
},
func(request *user.AddPersonalAccessTokenRequest) error { return nil },
},
wantErr: true,
},
{
name: "add pat, ok",
args: args{
&user.AddPersonalAccessTokenRequest{
ExpirationDate: expirationDate,
},
func(request *user.AddPersonalAccessTokenRequest) error {
request.UserId = userId
return nil
},
},
},
{
name: "add pat human, not ok",
args: args{
&user.AddPersonalAccessTokenRequest{
ExpirationDate: expirationDate,
},
func(request *user.AddPersonalAccessTokenRequest) error {
resp := Instance.CreateUserTypeHuman(IamCTX)
request.UserId = resp.Id
return nil
},
},
wantErr: true,
},
{
name: "add another pat, ok",
args: args{
&user.AddPersonalAccessTokenRequest{
ExpirationDate: expirationDate,
},
func(request *user.AddPersonalAccessTokenRequest) error {
request.UserId = userId
_, err := Client.AddPersonalAccessToken(IamCTX, &user.AddPersonalAccessTokenRequest{
ExpirationDate: expirationDate,
UserId: userId,
})
return err
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
now := time.Now()
err := tt.args.prepare(tt.args.req)
require.NoError(t, err)
got, err := Client.AddPersonalAccessToken(CTX, tt.args.req)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.NotEmpty(t, got.TokenId, "id is empty")
assert.NotEmpty(t, got.Token, "token is empty")
creationDate := got.CreationDate.AsTime()
assert.Greater(t, creationDate, now, "creation date is before the test started")
assert.Less(t, creationDate, time.Now(), "creation date is in the future")
})
}
}
func TestServer_AddPersonalAccessToken_Permission(t *testing.T) {
OrgCTX := CTX
otherOrg := Instance.CreateOrganization(IamCTX, fmt.Sprintf("AddPersonalAccessToken-%s", gofakeit.AppName()), gofakeit.Email())
otherOrgUser, err := Client.CreateUser(IamCTX, &user.CreateUserRequest{
OrganizationId: otherOrg.OrganizationId,
UserType: &user.CreateUserRequest_Machine_{
Machine: &user.CreateUserRequest_Machine{
Name: gofakeit.Name(),
},
},
})
require.NoError(t, err)
request := &user.AddPersonalAccessTokenRequest{
ExpirationDate: timestamppb.New(time.Now().Add(time.Hour * 24)),
UserId: otherOrgUser.GetId(),
}
type args struct {
ctx context.Context
req *user.AddPersonalAccessTokenRequest
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "system, ok",
args: args{SystemCTX, request},
},
{
name: "instance, ok",
args: args{IamCTX, request},
},
{
name: "org, error",
args: args{OrgCTX, request},
wantErr: true,
},
{
name: "user, error",
args: args{UserCTX, request},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
now := time.Now()
require.NoError(t, err)
got, err := Client.AddPersonalAccessToken(tt.args.ctx, tt.args.req)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.NotEmpty(t, got.TokenId, "id is empty")
assert.NotEmpty(t, got.Token, "token is empty")
creationDate := got.CreationDate.AsTime()
assert.Greater(t, creationDate, now, "creation date is before the test started")
assert.Less(t, creationDate, time.Now(), "creation date is in the future")
})
}
}
func TestServer_RemovePersonalAccessToken(t *testing.T) {
resp := Instance.CreateUserTypeMachine(IamCTX)
userId := resp.GetId()
expirationDate := timestamppb.New(time.Now().Add(time.Hour * 24))
type args struct {
req *user.RemovePersonalAccessTokenRequest
prepare func(request *user.RemovePersonalAccessTokenRequest) error
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "remove pat, user not existing",
args: args{
&user.RemovePersonalAccessTokenRequest{
UserId: "notexisting",
},
func(request *user.RemovePersonalAccessTokenRequest) error {
pat, err := Client.AddPersonalAccessToken(CTX, &user.AddPersonalAccessTokenRequest{
ExpirationDate: expirationDate,
UserId: userId,
})
request.TokenId = pat.GetTokenId()
return err
},
},
wantErr: true,
},
{
name: "remove pat, not existing",
args: args{
&user.RemovePersonalAccessTokenRequest{
TokenId: "notexisting",
},
func(request *user.RemovePersonalAccessTokenRequest) error {
request.UserId = userId
return nil
},
},
wantErr: true,
},
{
name: "remove pat, ok",
args: args{
&user.RemovePersonalAccessTokenRequest{},
func(request *user.RemovePersonalAccessTokenRequest) error {
pat, err := Client.AddPersonalAccessToken(CTX, &user.AddPersonalAccessTokenRequest{
ExpirationDate: expirationDate,
UserId: userId,
})
request.TokenId = pat.GetTokenId()
request.UserId = userId
return err
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
now := time.Now()
err := tt.args.prepare(tt.args.req)
require.NoError(t, err)
got, err := Client.RemovePersonalAccessToken(CTX, tt.args.req)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
deletionDate := got.DeletionDate.AsTime()
assert.Greater(t, deletionDate, now, "creation date is before the test started")
assert.Less(t, deletionDate, time.Now(), "creation date is in the future")
})
}
}
func TestServer_RemovePersonalAccessToken_Permission(t *testing.T) {
otherOrg := Instance.CreateOrganization(IamCTX, fmt.Sprintf("RemovePersonalAccessToken-%s", gofakeit.AppName()), gofakeit.Email())
otherOrgUser, err := Client.CreateUser(IamCTX, &user.CreateUserRequest{
OrganizationId: otherOrg.OrganizationId,
UserType: &user.CreateUserRequest_Machine_{
Machine: &user.CreateUserRequest_Machine{
Name: gofakeit.Name(),
},
},
})
request := &user.RemovePersonalAccessTokenRequest{
UserId: otherOrgUser.GetId(),
}
prepare := func(request *user.RemovePersonalAccessTokenRequest) error {
pat, err := Client.AddPersonalAccessToken(IamCTX, &user.AddPersonalAccessTokenRequest{
ExpirationDate: timestamppb.New(time.Now().Add(time.Hour * 24)),
UserId: otherOrgUser.GetId(),
})
request.TokenId = pat.GetTokenId()
return err
}
require.NoError(t, err)
type args struct {
ctx context.Context
req *user.RemovePersonalAccessTokenRequest
prepare func(request *user.RemovePersonalAccessTokenRequest) error
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "system, ok",
args: args{SystemCTX, request, prepare},
},
{
name: "instance, ok",
args: args{IamCTX, request, prepare},
},
{
name: "org, error",
args: args{CTX, request, prepare},
wantErr: true,
},
{
name: "user, error",
args: args{UserCTX, request, prepare},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
now := time.Now()
require.NoError(t, tt.args.prepare(tt.args.req))
got, err := Client.RemovePersonalAccessToken(tt.args.ctx, tt.args.req)
if tt.wantErr {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.NotEmpty(t, got.DeletionDate, "client pat is empty")
creationDate := got.DeletionDate.AsTime()
assert.Greater(t, creationDate, now, "creation date is before the test started")
assert.Less(t, creationDate, time.Now(), "creation date is in the future")
})
}
}
func TestServer_ListPersonalAccessTokens(t *testing.T) {
type args struct {
ctx context.Context
req *user.ListPersonalAccessTokensRequest
}
type testCase struct {
name string
args args
want *user.ListPersonalAccessTokensResponse
}
OrgCTX := CTX
otherOrg := Instance.CreateOrganization(SystemCTX, fmt.Sprintf("ListPersonalAccessTokens-%s", gofakeit.AppName()), gofakeit.Email())
otherOrgUser, err := Client.CreateUser(SystemCTX, &user.CreateUserRequest{
OrganizationId: otherOrg.OrganizationId,
UserType: &user.CreateUserRequest_Machine_{
Machine: &user.CreateUserRequest_Machine{
Name: gofakeit.Name(),
},
},
})
require.NoError(t, err)
otherOrgUserId := otherOrgUser.GetId()
otherUserId := Instance.CreateUserTypeMachine(SystemCTX).GetId()
onlySinceTestStartFilter := &user.PersonalAccessTokensSearchFilter{Filter: &user.PersonalAccessTokensSearchFilter_CreatedDateFilter{CreatedDateFilter: &filter.TimestampFilter{
Timestamp: timestamppb.Now(),
Method: filter.TimestampFilterMethod_TIMESTAMP_FILTER_METHOD_AFTER_OR_EQUALS,
}}}
myOrgId := Instance.DefaultOrg.GetId()
myUserId := Instance.Users.Get(integration.UserTypeNoPermission).ID
expiresInADay := time.Now().Truncate(time.Hour).Add(time.Hour * 24)
myDataPoint := setupPATDataPoint(t, myUserId, myOrgId, expiresInADay)
otherUserDataPoint := setupPATDataPoint(t, otherUserId, myOrgId, expiresInADay)
otherOrgDataPointExpiringSoon := setupPATDataPoint(t, otherOrgUserId, otherOrg.OrganizationId, time.Now().Truncate(time.Hour).Add(time.Hour))
otherOrgDataPointExpiringLate := setupPATDataPoint(t, otherOrgUserId, otherOrg.OrganizationId, expiresInADay.Add(time.Hour*24*30))
sortingColumnExpirationDate := user.PersonalAccessTokenFieldName_PERSONAL_ACCESS_TOKEN_FIELD_NAME_EXPIRATION_DATE
awaitPersonalAccessTokens(t,
onlySinceTestStartFilter,
otherOrgDataPointExpiringSoon.GetId(),
otherOrgDataPointExpiringLate.GetId(),
otherUserDataPoint.GetId(),
myDataPoint.GetId(),
)
tests := []testCase{
{
name: "list all, instance",
args: args{
IamCTX,
&user.ListPersonalAccessTokensRequest{
Filters: []*user.PersonalAccessTokensSearchFilter{onlySinceTestStartFilter},
},
},
want: &user.ListPersonalAccessTokensResponse{
Result: []*user.PersonalAccessToken{
otherOrgDataPointExpiringLate,
otherOrgDataPointExpiringSoon,
otherUserDataPoint,
myDataPoint,
},
Pagination: &filter.PaginationResponse{
TotalResult: 4,
AppliedLimit: 100,
},
},
},
{
name: "list all, org",
args: args{
OrgCTX,
&user.ListPersonalAccessTokensRequest{
Filters: []*user.PersonalAccessTokensSearchFilter{onlySinceTestStartFilter},
},
},
want: &user.ListPersonalAccessTokensResponse{
Result: []*user.PersonalAccessToken{
otherUserDataPoint,
myDataPoint,
},
Pagination: &filter.PaginationResponse{
TotalResult: 2,
AppliedLimit: 100,
},
},
},
{
name: "list all, user",
args: args{
UserCTX,
&user.ListPersonalAccessTokensRequest{
Filters: []*user.PersonalAccessTokensSearchFilter{onlySinceTestStartFilter},
},
},
want: &user.ListPersonalAccessTokensResponse{
Result: []*user.PersonalAccessToken{
myDataPoint,
},
Pagination: &filter.PaginationResponse{
TotalResult: 1,
AppliedLimit: 100,
},
},
},
{
name: "list by id",
args: args{
IamCTX,
&user.ListPersonalAccessTokensRequest{
Filters: []*user.PersonalAccessTokensSearchFilter{
onlySinceTestStartFilter,
{
Filter: &user.PersonalAccessTokensSearchFilter_TokenIdFilter{
TokenIdFilter: &filter.IDFilter{Id: otherOrgDataPointExpiringSoon.Id},
},
},
},
},
},
want: &user.ListPersonalAccessTokensResponse{
Result: []*user.PersonalAccessToken{
otherOrgDataPointExpiringSoon,
},
Pagination: &filter.PaginationResponse{
TotalResult: 1,
AppliedLimit: 100,
},
},
},
{
name: "list all from other org",
args: args{
IamCTX,
&user.ListPersonalAccessTokensRequest{
Filters: []*user.PersonalAccessTokensSearchFilter{
onlySinceTestStartFilter,
{
Filter: &user.PersonalAccessTokensSearchFilter_OrganizationIdFilter{
OrganizationIdFilter: &filter.IDFilter{Id: otherOrg.OrganizationId},
},
}},
},
},
want: &user.ListPersonalAccessTokensResponse{
Result: []*user.PersonalAccessToken{
otherOrgDataPointExpiringLate,
otherOrgDataPointExpiringSoon,
},
Pagination: &filter.PaginationResponse{
TotalResult: 2,
AppliedLimit: 100,
},
},
},
{
name: "sort by next expiration dates",
args: args{
IamCTX,
&user.ListPersonalAccessTokensRequest{
Pagination: &filter.PaginationRequest{
Asc: true,
},
SortingColumn: &sortingColumnExpirationDate,
Filters: []*user.PersonalAccessTokensSearchFilter{
onlySinceTestStartFilter,
{Filter: &user.PersonalAccessTokensSearchFilter_OrganizationIdFilter{OrganizationIdFilter: &filter.IDFilter{Id: otherOrg.OrganizationId}}},
},
},
},
want: &user.ListPersonalAccessTokensResponse{
Result: []*user.PersonalAccessToken{
otherOrgDataPointExpiringSoon,
otherOrgDataPointExpiringLate,
},
Pagination: &filter.PaginationResponse{
TotalResult: 2,
AppliedLimit: 100,
},
},
},
{
name: "get page",
args: args{
IamCTX,
&user.ListPersonalAccessTokensRequest{
Pagination: &filter.PaginationRequest{
Offset: 2,
Limit: 2,
Asc: true,
},
Filters: []*user.PersonalAccessTokensSearchFilter{
onlySinceTestStartFilter,
},
},
},
want: &user.ListPersonalAccessTokensResponse{
Result: []*user.PersonalAccessToken{
otherOrgDataPointExpiringSoon,
otherOrgDataPointExpiringLate,
},
Pagination: &filter.PaginationResponse{
TotalResult: 4,
AppliedLimit: 2,
},
},
},
{
name: "empty list",
args: args{
UserCTX,
&user.ListPersonalAccessTokensRequest{
Filters: []*user.PersonalAccessTokensSearchFilter{
{
Filter: &user.PersonalAccessTokensSearchFilter_TokenIdFilter{
TokenIdFilter: &filter.IDFilter{Id: otherUserDataPoint.Id},
},
},
},
},
},
want: &user.ListPersonalAccessTokensResponse{
Result: []*user.PersonalAccessToken{},
Pagination: &filter.PaginationResponse{
TotalResult: 0,
AppliedLimit: 100,
},
},
},
}
t.Run("with permission flag v2", func(t *testing.T) {
setPermissionCheckV2Flag(t, true)
defer setPermissionCheckV2Flag(t, false)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Client.ListPersonalAccessTokens(tt.args.ctx, tt.args.req)
require.NoError(t, err)
assert.Len(t, got.Result, len(tt.want.Result))
if diff := cmp.Diff(tt.want, got, protocmp.Transform()); diff != "" {
t.Errorf("ListPersonalAccessTokens() mismatch (-want +got):\n%s", diff)
}
})
}
})
t.Run("without permission flag v2", func(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Client.ListPersonalAccessTokens(tt.args.ctx, tt.args.req)
require.NoError(t, err)
assert.Len(t, got.Result, len(tt.want.Result))
// ignore the total result, as this is a known bug with the in-memory permission checks.
// The command can't know how many keys exist in the system if the SQL statement has a limit.
// This is fixed, once the in-memory permission checks are removed with https://github.com/zitadel/zitadel/issues/9188
tt.want.Pagination.TotalResult = got.Pagination.TotalResult
if diff := cmp.Diff(tt.want, got, protocmp.Transform()); diff != "" {
t.Errorf("ListPersonalAccessTokens() mismatch (-want +got):\n%s", diff)
}
})
}
})
}
func setupPATDataPoint(t *testing.T, userId, orgId string, expirationDate time.Time) *user.PersonalAccessToken {
expirationDatePb := timestamppb.New(expirationDate)
newPersonalAccessToken, err := Client.AddPersonalAccessToken(SystemCTX, &user.AddPersonalAccessTokenRequest{
UserId: userId,
ExpirationDate: expirationDatePb,
})
require.NoError(t, err)
return &user.PersonalAccessToken{
CreationDate: newPersonalAccessToken.CreationDate,
ChangeDate: newPersonalAccessToken.CreationDate,
Id: newPersonalAccessToken.GetTokenId(),
UserId: userId,
OrganizationId: orgId,
ExpirationDate: expirationDatePb,
}
}
func awaitPersonalAccessTokens(t *testing.T, sinceTestStartFilter *user.PersonalAccessTokensSearchFilter, patIds ...string) {
sortingColumn := user.PersonalAccessTokenFieldName_PERSONAL_ACCESS_TOKEN_FIELD_NAME_ID
slices.Sort(patIds)
require.EventuallyWithT(t, func(collect *assert.CollectT) {
result, err := Client.ListPersonalAccessTokens(SystemCTX, &user.ListPersonalAccessTokensRequest{
Filters: []*user.PersonalAccessTokensSearchFilter{sinceTestStartFilter},
SortingColumn: &sortingColumn,
Pagination: &filter.PaginationRequest{
Asc: true,
},
})
require.NoError(t, err)
if !assert.Len(collect, result.Result, len(patIds)) {
return
}
for i := range patIds {
patId := patIds[i]
require.Equal(collect, patId, result.Result[i].GetId())
}
}, 5*time.Second, time.Second, "pat not created in time")
}