feat(api): move application service v2beta to GA (and deprecate v2beta) (#10846)

# Which Problems Are Solved

As part of our efforts to simplify the structure and versions of our
APIs, were moving all existing v2beta endpoints to v2 and deprecate
them. They will be removed in Zitadel V5.

# How the Problems Are Solved

- This PR moves app v2beta service and its endpoints to a corresponding
to application v2 version. The v2beta service and endpoints are
deprecated.
- The comments and have been improved and, where not already done, moved
from swagger annotations to proto.
- All required fields have been marked with (google.api.field_behavior)
= REQUIRED and validation rules have been added where missing.
- Name ID of the application always `application_id`, previously was
also `id` and `app_id`.
- Get rid of all `app` abbreviations and name it `application` including
the service name, `AppState` -> `ApplicationState` and `AppSorting` ->
`ApplicationSorting`
- Updated `CreateApplicationRequest`:
- renamed `creation_request_type` to `application_type` and all its
options to `XY_configuration` instead of `XY_request`
- `RegenerateClientSecret`
  - renamed method to `GenerateClientSecret`
  - removed `app_type` from request
- `ListApplicationRequest`:
  - removed required `project_id` and provided it as a filter
- Type `ApplicationNameQuery` has been renamed to
`ApplicationNameFilter` as its usage in the request
- Renamed all fields and types from `config` to `configuration`
- Updated `DeleteApplicationKeyRequest`
  - removed `organization_id`
- Updated `GetApplicationKeyRequest`:
  - removed `project_id`, `application_id` and `organization_id``
- Updated `ListApplicationKeysRequest`:
  - removed oneOf `resource_id` and moved the options into filters
- Name ID of the application key always `key_id`.
- removed unnecessary package prefixed (`zitadel.application.v2`)
- formatted using `buf`

# Additional Changes

None

# Additional Context

- part of https://github.com/zitadel/zitadel/issues/10772
- requires backport to v4.x
This commit is contained in:
Livio Spring
2025-10-17 10:12:20 +02:00
committed by GitHub
parent 7e11f7a032
commit 0281670030
36 changed files with 7475 additions and 31 deletions
@@ -0,0 +1,206 @@
//go:build integration
package app_test
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/integration"
"github.com/zitadel/zitadel/pkg/grpc/application/v2"
)
func TestCreateApplicationKey(t *testing.T) {
p, projectOwnerCtx := getProjectAndProjectContext(t, instance, IAMOwnerCtx)
createdApp := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
t.Parallel()
tt := []struct {
testName string
creationRequest *application.CreateApplicationKeyRequest
inputCtx context.Context
expectedErrorType codes.Code
}{
{
testName: "when application id is not found should return failed precondition",
inputCtx: IAMOwnerCtx,
creationRequest: &application.CreateApplicationKeyRequest{
ProjectId: p.GetId(),
ApplicationId: integration.ID(),
ExpirationDate: timestamppb.New(time.Now().AddDate(0, 0, 1).UTC()),
},
expectedErrorType: codes.FailedPrecondition,
},
{
testName: "when CreateAPIApp request is valid should create application and return no error",
inputCtx: IAMOwnerCtx,
creationRequest: &application.CreateApplicationKeyRequest{
ProjectId: p.GetId(),
ApplicationId: createdApp.GetApplicationId(),
ExpirationDate: timestamppb.New(time.Now().AddDate(0, 0, 1).UTC()),
},
},
// LoginUser
{
testName: "when user has no project.application.write permission for application key generation should return permission error",
inputCtx: LoginUserCtx,
creationRequest: &application.CreateApplicationKeyRequest{
ProjectId: p.GetId(),
ApplicationId: createdApp.GetApplicationId(),
ExpirationDate: timestamppb.New(time.Now().AddDate(0, 0, 1).UTC()),
},
expectedErrorType: codes.PermissionDenied,
},
// OrgOwner
{
testName: "when user is OrgOwner application key request should succeed",
inputCtx: OrgOwnerCtx,
creationRequest: &application.CreateApplicationKeyRequest{
ProjectId: p.GetId(),
ApplicationId: createdApp.GetApplicationId(),
ExpirationDate: timestamppb.New(time.Now().AddDate(0, 0, 1).UTC()),
},
},
// ProjectOwner
{
testName: "when user is ProjectOwner application key request should succeed",
inputCtx: projectOwnerCtx,
creationRequest: &application.CreateApplicationKeyRequest{
ProjectId: p.GetId(),
ApplicationId: createdApp.GetApplicationId(),
ExpirationDate: timestamppb.New(time.Now().AddDate(0, 0, 1).UTC()),
},
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
t.Parallel()
res, err := instance.Client.ApplicationV2.CreateApplicationKey(tc.inputCtx, tc.creationRequest)
require.Equal(t, tc.expectedErrorType, status.Code(err))
if tc.expectedErrorType == codes.OK {
assert.NotZero(t, res.GetKeyId())
assert.NotZero(t, res.GetCreationDate())
assert.NotZero(t, res.GetKeyDetails())
}
})
}
}
func TestDeleteApplicationKey(t *testing.T) {
p, projectOwnerCtx := getProjectAndProjectContext(t, instance, IAMOwnerCtx)
createdApp := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
t.Parallel()
tt := []struct {
testName string
deletionRequest func(ttt *testing.T) *application.DeleteApplicationKeyRequest
inputCtx context.Context
expectedErrorType codes.Code
}{
{
testName: "when application key ID is not found should return not found error",
inputCtx: IAMOwnerCtx,
deletionRequest: func(ttt *testing.T) *application.DeleteApplicationKeyRequest {
return &application.DeleteApplicationKeyRequest{
KeyId: integration.ID(),
ProjectId: p.GetId(),
ApplicationId: createdApp.GetApplicationId(),
}
},
expectedErrorType: codes.NotFound,
},
{
testName: "when valid application key ID should delete successfully",
inputCtx: IAMOwnerCtx,
deletionRequest: func(ttt *testing.T) *application.DeleteApplicationKeyRequest {
createdAppKey := createAppKey(ttt, IAMOwnerCtx, instance, p.GetId(), createdApp.GetApplicationId(), time.Now().AddDate(0, 0, 1))
return &application.DeleteApplicationKeyRequest{
KeyId: createdAppKey.GetKeyId(),
ProjectId: p.GetId(),
ApplicationId: createdApp.GetApplicationId(),
}
},
},
// LoginUser
{
testName: "when user has no project.application.write permission for application key deletion should return permission error",
inputCtx: LoginUserCtx,
deletionRequest: func(ttt *testing.T) *application.DeleteApplicationKeyRequest {
createdAppKey := createAppKey(ttt, IAMOwnerCtx, instance, p.GetId(), createdApp.GetApplicationId(), time.Now().AddDate(0, 0, 1))
return &application.DeleteApplicationKeyRequest{
KeyId: createdAppKey.GetKeyId(),
ProjectId: p.GetId(),
ApplicationId: createdApp.GetApplicationId(),
}
},
expectedErrorType: codes.PermissionDenied,
},
// ProjectOwner
{
testName: "when user is OrgOwner API request should succeed",
inputCtx: projectOwnerCtx,
deletionRequest: func(ttt *testing.T) *application.DeleteApplicationKeyRequest {
createdAppKey := createAppKey(ttt, IAMOwnerCtx, instance, p.GetId(), createdApp.GetApplicationId(), time.Now().AddDate(0, 0, 1))
return &application.DeleteApplicationKeyRequest{
KeyId: createdAppKey.GetKeyId(),
ProjectId: p.GetId(),
ApplicationId: createdApp.GetApplicationId(),
}
},
},
// OrganizationOwner
{
testName: "when user is OrgOwner application key deletion request should succeed",
inputCtx: OrgOwnerCtx,
deletionRequest: func(ttt *testing.T) *application.DeleteApplicationKeyRequest {
createdAppKey := createAppKey(ttt, IAMOwnerCtx, instance, p.GetId(), createdApp.GetApplicationId(), time.Now().AddDate(0, 0, 1))
return &application.DeleteApplicationKeyRequest{
KeyId: createdAppKey.GetKeyId(),
ProjectId: p.GetId(),
ApplicationId: createdApp.GetApplicationId(),
}
},
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
t.Parallel()
// Given
deletionReq := tc.deletionRequest(t)
// When
res, err := instance.Client.ApplicationV2.DeleteApplicationKey(tc.inputCtx, deletionReq)
// Then
require.Equal(t, tc.expectedErrorType, status.Code(err))
if tc.expectedErrorType == codes.OK {
assert.NotEmpty(t, res.GetDeletionDate())
}
})
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,913 @@
//go:build integration
package app_test
import (
"context"
"fmt"
"slices"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/zitadel/zitadel/internal/integration"
"github.com/zitadel/zitadel/pkg/grpc/application/v2"
"github.com/zitadel/zitadel/pkg/grpc/filter/v2"
)
func TestGetApplication(t *testing.T) {
p, projectOwnerCtx := getProjectAndProjectContext(t, instance, IAMOwnerCtx)
apiAppName := integration.ApplicationName()
createdApiApp, errAPIAppCreation := instance.Client.ApplicationV2.CreateApplication(IAMOwnerCtx, &application.CreateApplicationRequest{
ProjectId: p.GetId(),
Name: apiAppName,
ApplicationType: &application.CreateApplicationRequest_ApiConfiguration{
ApiConfiguration: &application.CreateAPIApplicationRequest{
AuthMethodType: application.APIAuthMethodType_API_AUTH_METHOD_TYPE_BASIC,
},
},
})
require.Nil(t, errAPIAppCreation)
samlAppName := integration.ApplicationName()
createdSAMLApp, errSAMLAppCreation := instance.Client.ApplicationV2.CreateApplication(IAMOwnerCtx, &application.CreateApplicationRequest{
ProjectId: p.GetId(),
Name: samlAppName,
ApplicationType: &application.CreateApplicationRequest_SamlConfiguration{
SamlConfiguration: &application.CreateSAMLApplicationRequest{
LoginVersion: &application.LoginVersion{Version: &application.LoginVersion_LoginV1{LoginV1: &application.LoginV1{}}},
Metadata: &application.CreateSAMLApplicationRequest_MetadataXml{MetadataXml: samlMetadataGen(integration.URL())},
},
},
})
require.Nil(t, errSAMLAppCreation)
oidcAppName := integration.ApplicationName()
createdOIDCApp, errOIDCAppCreation := instance.Client.ApplicationV2.CreateApplication(IAMOwnerCtx, &application.CreateApplicationRequest{
ProjectId: p.GetId(),
Name: oidcAppName,
ApplicationType: &application.CreateApplicationRequest_OidcConfiguration{
OidcConfiguration: &application.CreateOIDCApplicationRequest{
RedirectUris: []string{"http://example.com"},
ResponseTypes: []application.OIDCResponseType{application.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE},
GrantTypes: []application.OIDCGrantType{application.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE},
ApplicationType: application.OIDCApplicationType_OIDC_APP_TYPE_WEB,
AuthMethodType: application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC,
PostLogoutRedirectUris: []string{"http://example.com/home"},
Version: application.OIDCVersion_OIDC_VERSION_1_0,
AccessTokenType: application.OIDCTokenType_OIDC_TOKEN_TYPE_JWT,
BackChannelLogoutUri: "http://example.com/logout",
LoginVersion: &application.LoginVersion{Version: &application.LoginVersion_LoginV2{LoginV2: &application.LoginV2{BaseUri: &baseURI}}},
},
},
})
require.Nil(t, errOIDCAppCreation)
t.Parallel()
tt := []struct {
testName string
inputRequest *application.GetApplicationRequest
inputCtx context.Context
expectedErrorType codes.Code
expectedAppName string
expectedAppID string
expectedApplicationType string
}{
{
testName: "when unknown application ID should return not found error",
inputCtx: IAMOwnerCtx,
inputRequest: &application.GetApplicationRequest{
ApplicationId: integration.ID(),
},
expectedErrorType: codes.NotFound,
},
{
testName: "when user has no permission should return membership not found error",
inputCtx: NoPermissionCtx,
inputRequest: &application.GetApplicationRequest{
ApplicationId: createdApiApp.GetApplicationId(),
},
expectedErrorType: codes.NotFound,
},
{
testName: "when providing API application ID should return valid API application result",
inputCtx: projectOwnerCtx,
inputRequest: &application.GetApplicationRequest{
ApplicationId: createdApiApp.GetApplicationId(),
},
expectedAppName: apiAppName,
expectedAppID: createdApiApp.GetApplicationId(),
expectedApplicationType: fmt.Sprintf("%T", &application.Application_ApiConfiguration{}),
},
{
testName: "when providing SAML application ID should return valid SAML application result",
inputCtx: IAMOwnerCtx,
inputRequest: &application.GetApplicationRequest{
ApplicationId: createdSAMLApp.GetApplicationId(),
},
expectedAppName: samlAppName,
expectedAppID: createdSAMLApp.GetApplicationId(),
expectedApplicationType: fmt.Sprintf("%T", &application.Application_SamlConfiguration{}),
},
{
testName: "when providing OIDC application ID should return valid OIDC application result",
inputCtx: IAMOwnerCtx,
inputRequest: &application.GetApplicationRequest{
ApplicationId: createdOIDCApp.GetApplicationId(),
},
expectedAppName: oidcAppName,
expectedAppID: createdOIDCApp.GetApplicationId(),
expectedApplicationType: fmt.Sprintf("%T", &application.Application_OidcConfiguration{}),
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
t.Parallel()
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputCtx, time.Minute)
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
// When
res, err := instance.Client.ApplicationV2.GetApplication(tc.inputCtx, tc.inputRequest)
// Then
require.Equal(t, tc.expectedErrorType, status.Code(err))
if tc.expectedErrorType == codes.OK {
assert.Equal(t, tc.expectedAppID, res.GetApplication().GetApplicationId())
assert.Equal(t, tc.expectedAppName, res.GetApplication().GetName())
assert.NotZero(t, res.GetApplication().GetCreationDate())
assert.NotZero(t, res.GetApplication().GetChangeDate())
appType := fmt.Sprintf("%T", res.GetApplication().GetConfiguration())
assert.Equal(t, tc.expectedApplicationType, appType)
}
}, retryDuration, tick)
})
}
}
func TestListApplications(t *testing.T) {
p, projectOwnerCtx := getProjectAndProjectContext(t, instance, IAMOwnerCtx)
t.Parallel()
createdApiApp, apiAppName := createAPIAppWithName(t, IAMOwnerCtx, instance, p.GetId())
createdDeactivatedApiApp, deactivatedApiAppName := createAPIAppWithName(t, IAMOwnerCtx, instance, p.GetId())
deactivateApp(t, createdDeactivatedApiApp, p.GetId())
_, createdSAMLApp, samlAppName := createSAMLAppWithName(t, integration.URL(), p.GetId())
createdOIDCApp, oidcAppName := createOIDCAppWithName(t, integration.URL(), p.GetId())
type appWithName struct {
app *application.CreateApplicationResponse
name string
}
// Sorting
appsSortedByName := []appWithName{
{name: apiAppName, app: createdApiApp},
{name: deactivatedApiAppName, app: createdDeactivatedApiApp},
{name: samlAppName, app: createdSAMLApp},
{name: oidcAppName, app: createdOIDCApp},
}
slices.SortFunc(appsSortedByName, func(a, b appWithName) int {
if a.name < b.name {
return -1
}
if a.name > b.name {
return 1
}
return 0
})
appsSortedByID := []appWithName{
{name: apiAppName, app: createdApiApp},
{name: deactivatedApiAppName, app: createdDeactivatedApiApp},
{name: samlAppName, app: createdSAMLApp},
{name: oidcAppName, app: createdOIDCApp},
}
slices.SortFunc(appsSortedByID, func(a, b appWithName) int {
if a.app.GetApplicationId() < b.app.GetApplicationId() {
return -1
}
if a.app.GetApplicationId() > b.app.GetApplicationId() {
return 1
}
return 0
})
appsSortedByCreationDate := []appWithName{
{name: apiAppName, app: createdApiApp},
{name: deactivatedApiAppName, app: createdDeactivatedApiApp},
{name: samlAppName, app: createdSAMLApp},
{name: oidcAppName, app: createdOIDCApp},
}
slices.SortFunc(appsSortedByCreationDate, func(a, b appWithName) int {
aCreationDate := a.app.GetCreationDate().AsTime()
bCreationDate := b.app.GetCreationDate().AsTime()
if aCreationDate.Before(bCreationDate) {
return -1
}
if bCreationDate.Before(aCreationDate) {
return 1
}
return 0
})
tt := []struct {
testName string
inputRequest *application.ListApplicationsRequest
inputCtx context.Context
expectedOrderedList []appWithName
expectedOrderedKeys func(keys []appWithName) any
actualOrderedKeys func(keys []*application.Application) any
}{
{
testName: "when no apps found should return empty list",
inputCtx: IAMOwnerCtx,
inputRequest: &application.ListApplicationsRequest{
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: "another-id"},
},
},
},
},
expectedOrderedList: []appWithName{},
expectedOrderedKeys: func(keys []appWithName) any { return keys },
actualOrderedKeys: func(keys []*application.Application) any { return keys },
},
{
testName: "when user has no read permission should return empty set",
inputCtx: NoPermissionCtx,
inputRequest: &application.ListApplicationsRequest{
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
},
},
},
},
expectedOrderedList: []appWithName{},
expectedOrderedKeys: func(keys []appWithName) any { return keys },
actualOrderedKeys: func(keys []*application.Application) any { return keys },
},
{
testName: "when sorting by name should return apps sorted by name in descending order",
inputCtx: IAMOwnerCtx,
inputRequest: &application.ListApplicationsRequest{
SortingColumn: application.ApplicationSorting_APPLICATION_SORT_BY_NAME,
Pagination: &filter.PaginationRequest{Asc: true},
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
},
},
},
},
expectedOrderedList: appsSortedByName,
expectedOrderedKeys: func(apps []appWithName) any {
names := make([]string, len(apps))
for i, a := range apps {
names[i] = a.name
}
return names
},
actualOrderedKeys: func(apps []*application.Application) any {
names := make([]string, len(apps))
for i, a := range apps {
names[i] = a.GetName()
}
return names
},
},
{
testName: "when user is project owner should return apps sorted by name in ascending order",
inputCtx: projectOwnerCtx,
inputRequest: &application.ListApplicationsRequest{
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
},
},
},
SortingColumn: application.ApplicationSorting_APPLICATION_SORT_BY_NAME,
Pagination: &filter.PaginationRequest{Asc: true},
},
expectedOrderedList: appsSortedByName,
expectedOrderedKeys: func(apps []appWithName) any {
names := make([]string, len(apps))
for i, a := range apps {
names[i] = a.name
}
return names
},
actualOrderedKeys: func(apps []*application.Application) any {
names := make([]string, len(apps))
for i, a := range apps {
names[i] = a.GetName()
}
return names
},
},
{
testName: "when sorting by id should return apps sorted by id in descending order",
inputCtx: IAMOwnerCtx,
inputRequest: &application.ListApplicationsRequest{
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
},
},
},
SortingColumn: application.ApplicationSorting_APPLICATION_SORT_BY_ID,
Pagination: &filter.PaginationRequest{Asc: true},
},
expectedOrderedList: appsSortedByID,
expectedOrderedKeys: func(apps []appWithName) any {
ids := make([]string, len(apps))
for i, a := range apps {
ids[i] = a.app.GetApplicationId()
}
return ids
},
actualOrderedKeys: func(apps []*application.Application) any {
ids := make([]string, len(apps))
for i, a := range apps {
ids[i] = a.GetApplicationId()
}
return ids
},
},
{
testName: "when sorting by creation date should return apps sorted by creation date in descending order",
inputCtx: IAMOwnerCtx,
inputRequest: &application.ListApplicationsRequest{
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
},
},
},
SortingColumn: application.ApplicationSorting_APPLICATION_SORT_BY_CREATION_DATE,
Pagination: &filter.PaginationRequest{Asc: true},
},
expectedOrderedList: appsSortedByCreationDate,
expectedOrderedKeys: func(apps []appWithName) any {
creationDates := make([]time.Time, len(apps))
for i, a := range apps {
creationDates[i] = a.app.GetCreationDate().AsTime()
}
return creationDates
},
actualOrderedKeys: func(apps []*application.Application) any {
creationDates := make([]time.Time, len(apps))
for i, a := range apps {
creationDates[i] = a.GetCreationDate().AsTime()
}
return creationDates
},
},
{
testName: "when filtering by active apps should return active apps only",
inputCtx: IAMOwnerCtx,
inputRequest: &application.ListApplicationsRequest{
Pagination: &filter.PaginationRequest{Asc: true},
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
},
},
{
Filter: &application.ApplicationSearchFilter_StateFilter{
StateFilter: application.ApplicationState_APPLICATION_STATE_ACTIVE,
},
},
},
},
expectedOrderedList: slices.DeleteFunc(
slices.Clone(appsSortedByID),
func(a appWithName) bool { return a.name == deactivatedApiAppName },
),
expectedOrderedKeys: func(apps []appWithName) any {
creationDates := make([]time.Time, len(apps))
for i, a := range apps {
creationDates[i] = a.app.GetCreationDate().AsTime()
}
return creationDates
},
actualOrderedKeys: func(apps []*application.Application) any {
creationDates := make([]time.Time, len(apps))
for i, a := range apps {
creationDates[i] = a.GetCreationDate().AsTime()
}
return creationDates
},
},
{
testName: "when filtering by application type should return apps of matching type only",
inputCtx: IAMOwnerCtx,
inputRequest: &application.ListApplicationsRequest{
Pagination: &filter.PaginationRequest{Asc: true},
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
},
},
{Filter: &application.ApplicationSearchFilter_TypeFilter{
TypeFilter: application.ApplicationType_APPLICATION_TYPE_OIDC,
}},
},
},
expectedOrderedList: slices.DeleteFunc(
slices.Clone(appsSortedByID),
func(a appWithName) bool { return a.name != oidcAppName },
),
expectedOrderedKeys: func(apps []appWithName) any {
creationDates := make([]time.Time, len(apps))
for i, a := range apps {
creationDates[i] = a.app.GetCreationDate().AsTime()
}
return creationDates
},
actualOrderedKeys: func(apps []*application.Application) any {
creationDates := make([]time.Time, len(apps))
for i, a := range apps {
creationDates[i] = a.GetCreationDate().AsTime()
}
return creationDates
},
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
t.Parallel()
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputCtx, time.Minute)
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
// When
res, err := instance.Client.ApplicationV2.ListApplications(tc.inputCtx, tc.inputRequest)
// Then
require.Equal(ttt, codes.OK, status.Code(err))
if err == nil {
assert.Len(ttt, res.GetApplications(), len(tc.expectedOrderedList))
actualOrderedKeys := tc.actualOrderedKeys(res.GetApplications())
expectedOrderedKeys := tc.expectedOrderedKeys(tc.expectedOrderedList)
assert.ElementsMatch(ttt, expectedOrderedKeys, actualOrderedKeys)
}
}, retryDuration, tick)
})
}
}
func TestListApplications_WithPermissionV2(t *testing.T) {
ensureFeaturePermissionV2Enabled(t, instancePermissionV2)
iamOwnerCtx := instancePermissionV2.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
p, projectOwnerCtx := getProjectAndProjectContext(t, instancePermissionV2, iamOwnerCtx)
_, otherProjectOwnerCtx := getProjectAndProjectContext(t, instancePermissionV2, iamOwnerCtx)
appName1, appName2, appName3 := integration.ApplicationName(), integration.ApplicationName(), integration.ApplicationName()
reqForAPIAppCreation := &application.CreateApplicationRequest_ApiConfiguration{
ApiConfiguration: &application.CreateAPIApplicationRequest{AuthMethodType: application.APIAuthMethodType_API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT},
}
app1, appAPIConfigChangeErr := instancePermissionV2.Client.ApplicationV2.CreateApplication(iamOwnerCtx, &application.CreateApplicationRequest{
ProjectId: p.GetId(),
Name: appName1,
ApplicationType: reqForAPIAppCreation,
})
require.Nil(t, appAPIConfigChangeErr)
app2, appAPIConfigChangeErr := instancePermissionV2.Client.ApplicationV2.CreateApplication(iamOwnerCtx, &application.CreateApplicationRequest{
ProjectId: p.GetId(),
Name: appName2,
ApplicationType: reqForAPIAppCreation,
})
require.Nil(t, appAPIConfigChangeErr)
app3, appAPIConfigChangeErr := instancePermissionV2.Client.ApplicationV2.CreateApplication(iamOwnerCtx, &application.CreateApplicationRequest{
ProjectId: p.GetId(),
Name: appName3,
ApplicationType: reqForAPIAppCreation,
})
require.Nil(t, appAPIConfigChangeErr)
t.Parallel()
tt := []struct {
testName string
inputRequest *application.ListApplicationsRequest
inputCtx context.Context
expectedCode codes.Code
expectedAppIDs []string
}{
{
testName: "when user has no read permission should return empty set",
inputCtx: instancePermissionV2.WithAuthorizationToken(context.Background(), integration.UserTypeNoPermission),
inputRequest: &application.ListApplicationsRequest{
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
},
},
},
},
expectedAppIDs: []string{},
},
{
testName: "when projectOwner should return full application list",
inputCtx: projectOwnerCtx,
inputRequest: &application.ListApplicationsRequest{
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
},
},
},
},
expectedCode: codes.OK,
expectedAppIDs: []string{app1.GetApplicationId(), app2.GetApplicationId(), app3.GetApplicationId()},
},
{
testName: "when orgOwner should return full application list",
inputCtx: instancePermissionV2.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner),
inputRequest: &application.ListApplicationsRequest{
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
},
},
},
},
expectedAppIDs: []string{app1.GetApplicationId(), app2.GetApplicationId(), app3.GetApplicationId()},
},
{
testName: "when iamOwner user should return full application list",
inputCtx: iamOwnerCtx,
inputRequest: &application.ListApplicationsRequest{
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
},
},
},
},
expectedAppIDs: []string{app1.GetApplicationId(), app2.GetApplicationId(), app3.GetApplicationId()},
},
{
testName: "when other projectOwner user should return empty list",
inputCtx: otherProjectOwnerCtx,
inputRequest: &application.ListApplicationsRequest{
Filters: []*application.ApplicationSearchFilter{
{
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
},
},
},
},
expectedAppIDs: []string{},
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
t.Parallel()
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputCtx, time.Minute)
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
// When
res, err := instancePermissionV2.Client.ApplicationV2.ListApplications(tc.inputCtx, tc.inputRequest)
// Then
require.Equal(ttt, tc.expectedCode, status.Code(err))
if err == nil {
require.Len(ttt, res.GetApplications(), len(tc.expectedAppIDs))
resAppIDs := []string{}
for _, a := range res.GetApplications() {
resAppIDs = append(resAppIDs, a.GetApplicationId())
}
assert.ElementsMatch(ttt, tc.expectedAppIDs, resAppIDs)
}
}, retryDuration, tick)
})
}
}
func TestGetApplicationKey(t *testing.T) {
p, projectOwnerCtx := getProjectAndProjectContext(t, instance, IAMOwnerCtx)
createdApiApp := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
createdAppKey := createAppKey(t, IAMOwnerCtx, instance, p.GetId(), createdApiApp.GetApplicationId(), time.Now().AddDate(0, 0, 1))
t.Parallel()
tt := []struct {
testName string
inputRequest *application.GetApplicationKeyRequest
inputCtx context.Context
expectedErrorType codes.Code
expectedAppKeyID string
}{
{
testName: "when unknown application ID should return not found error",
inputCtx: IAMOwnerCtx,
inputRequest: &application.GetApplicationKeyRequest{
KeyId: integration.ID(),
},
expectedErrorType: codes.NotFound,
},
{
testName: "when user has no permission should return membership not found error",
inputCtx: NoPermissionCtx,
inputRequest: &application.GetApplicationKeyRequest{
KeyId: createdAppKey.GetKeyId(),
},
expectedErrorType: codes.NotFound,
},
{
testName: "when providing API application ID should return valid API application result",
inputCtx: projectOwnerCtx,
inputRequest: &application.GetApplicationKeyRequest{
KeyId: createdAppKey.GetKeyId(),
},
expectedAppKeyID: createdAppKey.GetKeyId(),
},
{
testName: "when user is OrgOwner should return request key",
inputCtx: OrgOwnerCtx,
inputRequest: &application.GetApplicationKeyRequest{
KeyId: createdAppKey.GetKeyId(),
},
expectedAppKeyID: createdAppKey.GetKeyId(),
},
{
testName: "when user is IAMOwner should return request key",
inputCtx: OrgOwnerCtx,
inputRequest: &application.GetApplicationKeyRequest{
KeyId: createdAppKey.GetKeyId(),
},
expectedAppKeyID: createdAppKey.GetKeyId(),
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
t.Parallel()
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputCtx, time.Minute)
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
// When
res, err := instance.Client.ApplicationV2.GetApplicationKey(tc.inputCtx, tc.inputRequest)
// Then
require.Equal(t, tc.expectedErrorType, status.Code(err))
if tc.expectedErrorType == codes.OK {
assert.Equal(t, tc.expectedAppKeyID, res.GetKeyId())
assert.NotEmpty(t, res.GetCreationDate())
assert.NotEmpty(t, res.GetExpirationDate())
}
}, retryDuration, tick)
})
}
}
func TestListApplicationKeys(t *testing.T) {
p, projectOwnerCtx := getProjectAndProjectContext(t, instance, IAMOwnerCtx)
createdApiApp1 := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
createdApiApp2 := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
tomorrow := time.Now().AddDate(0, 0, 1)
in2Days := tomorrow.AddDate(0, 0, 1)
in3Days := in2Days.AddDate(0, 0, 1)
appKey1 := createAppKey(t, IAMOwnerCtx, instance, p.GetId(), createdApiApp1.GetApplicationId(), in2Days)
appKey2 := createAppKey(t, IAMOwnerCtx, instance, p.GetId(), createdApiApp1.GetApplicationId(), in3Days)
appKey3 := createAppKey(t, IAMOwnerCtx, instance, p.GetId(), createdApiApp1.GetApplicationId(), tomorrow)
appKey4 := createAppKey(t, IAMOwnerCtx, instance, p.GetId(), createdApiApp2.GetApplicationId(), tomorrow)
t.Parallel()
tt := []struct {
testName string
inputRequest *application.ListApplicationKeysRequest
deps func() (projectID, applicationID, organizationID string)
inputCtx context.Context
expectedErrorType codes.Code
expectedAppKeysIDs []string
}{
{
testName: "when sorting by expiration date should return keys sorted by expiration date ascending",
inputCtx: LoginUserCtx,
inputRequest: &application.ListApplicationKeysRequest{
Pagination: &filter.PaginationRequest{Asc: true},
SortingColumn: application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_EXPIRATION,
Filters: []*application.ApplicationKeySearchFilter{
{Filter: &application.ApplicationKeySearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ApplicationKeyProjectIDFilter{ProjectId: p.GetId()},
}},
},
},
expectedAppKeysIDs: []string{appKey3.GetKeyId(), appKey4.GetKeyId(), appKey1.GetKeyId(), appKey2.GetKeyId()},
},
{
testName: "when sorting by creation date should return keys sorted by creation date descending",
inputCtx: IAMOwnerCtx,
inputRequest: &application.ListApplicationKeysRequest{
SortingColumn: application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_CREATION_DATE,
Filters: []*application.ApplicationKeySearchFilter{
{Filter: &application.ApplicationKeySearchFilter_ProjectIdFilter{
ProjectIdFilter: &application.ApplicationKeyProjectIDFilter{ProjectId: p.GetId()},
}},
},
},
expectedAppKeysIDs: []string{appKey4.GetKeyId(), appKey3.GetKeyId(), appKey2.GetKeyId(), appKey1.GetKeyId()},
},
{
testName: "when filtering by application ID should return keys matching application ID sorted by ID",
inputCtx: projectOwnerCtx,
inputRequest: &application.ListApplicationKeysRequest{
Pagination: &filter.PaginationRequest{Asc: true},
Filters: []*application.ApplicationKeySearchFilter{
{Filter: &application.ApplicationKeySearchFilter_ApplicationIdFilter{
ApplicationIdFilter: &application.ApplicationKeyApplicationIDFilter{ApplicationId: createdApiApp1.GetApplicationId()},
}},
},
},
expectedAppKeysIDs: []string{appKey1.GetKeyId(), appKey2.GetKeyId(), appKey3.GetKeyId()},
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
t.Parallel()
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputCtx, time.Minute)
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
// When
res, err := instance.Client.ApplicationV2.ListApplicationKeys(tc.inputCtx, tc.inputRequest)
// Then
require.Equal(ttt, tc.expectedErrorType, status.Code(err))
if tc.expectedErrorType == codes.OK {
require.Len(ttt, res.GetKeys(), len(tc.expectedAppKeysIDs))
for i, k := range res.GetKeys() {
assert.Equal(ttt, tc.expectedAppKeysIDs[i], k.GetKeyId())
}
}
}, retryDuration, tick)
})
}
}
func TestListApplicationKeys_WithPermissionV2(t *testing.T) {
ensureFeaturePermissionV2Enabled(t, instancePermissionV2)
iamOwnerCtx := instancePermissionV2.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
loginUserCtx := instancePermissionV2.WithAuthorizationToken(context.Background(), integration.UserTypeLogin)
p, projectOwnerCtx := getProjectAndProjectContext(t, instancePermissionV2, iamOwnerCtx)
createdApiApp1 := createAPIApp(t, iamOwnerCtx, instancePermissionV2, p.GetId())
createdApiApp2 := createAPIApp(t, iamOwnerCtx, instancePermissionV2, p.GetId())
tomorrow := time.Now().AddDate(0, 0, 1)
in2Days := tomorrow.AddDate(0, 0, 1)
in3Days := in2Days.AddDate(0, 0, 1)
appKey1 := createAppKey(t, iamOwnerCtx, instancePermissionV2, p.GetId(), createdApiApp1.GetApplicationId(), in2Days)
appKey2 := createAppKey(t, iamOwnerCtx, instancePermissionV2, p.GetId(), createdApiApp1.GetApplicationId(), in3Days)
appKey3 := createAppKey(t, iamOwnerCtx, instancePermissionV2, p.GetId(), createdApiApp1.GetApplicationId(), tomorrow)
appKey4 := createAppKey(t, iamOwnerCtx, instancePermissionV2, p.GetId(), createdApiApp2.GetApplicationId(), tomorrow)
t.Parallel()
tt := []struct {
testName string
inputRequest *application.ListApplicationKeysRequest
deps func() (projectID, applicationID, organizationID string)
inputCtx context.Context
expectedErrorType codes.Code
expectedAppKeysIDs []string
}{
{
testName: "when sorting by expiration date should return keys sorted by expiration date ascending",
inputCtx: loginUserCtx,
inputRequest: &application.ListApplicationKeysRequest{
Pagination: &filter.PaginationRequest{Asc: true},
SortingColumn: application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_EXPIRATION,
},
expectedAppKeysIDs: []string{appKey3.GetKeyId(), appKey4.GetKeyId(), appKey1.GetKeyId(), appKey2.GetKeyId()},
},
{
testName: "when sorting by creation date should return keys sorted by creation date descending",
inputCtx: iamOwnerCtx,
inputRequest: &application.ListApplicationKeysRequest{
SortingColumn: application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_CREATION_DATE,
},
expectedAppKeysIDs: []string{appKey4.GetKeyId(), appKey3.GetKeyId(), appKey2.GetKeyId(), appKey1.GetKeyId()},
},
{
testName: "when filtering by application ID should return keys matching application ID sorted by ID",
inputCtx: projectOwnerCtx,
inputRequest: &application.ListApplicationKeysRequest{
Pagination: &filter.PaginationRequest{Asc: true},
Filters: []*application.ApplicationKeySearchFilter{
{Filter: &application.ApplicationKeySearchFilter_ApplicationIdFilter{
ApplicationIdFilter: &application.ApplicationKeyApplicationIDFilter{ApplicationId: createdApiApp1.GetApplicationId()},
}},
},
},
expectedAppKeysIDs: []string{appKey1.GetKeyId(), appKey2.GetKeyId(), appKey3.GetKeyId()},
},
}
for _, tc := range tt {
t.Run(tc.testName, func(t *testing.T) {
// t.Parallel()
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputCtx, time.Minute)
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
// When
res, err := instancePermissionV2.Client.ApplicationV2.ListApplicationKeys(tc.inputCtx, tc.inputRequest)
// Then
require.Equal(ttt, tc.expectedErrorType, status.Code(err))
if tc.expectedErrorType == codes.OK {
require.Len(ttt, res.GetKeys(), len(tc.expectedAppKeysIDs))
for i, k := range res.GetKeys() {
assert.Equal(ttt, tc.expectedAppKeysIDs[i], k.GetKeyId())
}
}
}, retryDuration, tick)
})
}
}
@@ -0,0 +1,219 @@
//go:build integration
package app_test
import (
"context"
"fmt"
"os"
"testing"
"time"
"github.com/muhlemmer/gu"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/integration"
"github.com/zitadel/zitadel/pkg/grpc/application/v2"
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
project_v2beta "github.com/zitadel/zitadel/pkg/grpc/project/v2beta"
)
var (
NoPermissionCtx context.Context
LoginUserCtx context.Context
OrgOwnerCtx context.Context
IAMOwnerCtx context.Context
instance *integration.Instance
instancePermissionV2 *integration.Instance
baseURI = "http://example.com"
)
func TestMain(m *testing.M) {
os.Exit(func() int {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
defer cancel()
instance = integration.NewInstance(ctx)
instancePermissionV2 = integration.NewInstance(ctx)
IAMOwnerCtx = instance.WithAuthorization(ctx, integration.UserTypeIAMOwner)
LoginUserCtx = instance.WithAuthorization(ctx, integration.UserTypeLogin)
OrgOwnerCtx = instance.WithAuthorization(ctx, integration.UserTypeOrgOwner)
NoPermissionCtx = instance.WithAuthorization(ctx, integration.UserTypeNoPermission)
return m.Run()
}())
}
func getProjectAndProjectContext(t *testing.T, inst *integration.Instance, ctx context.Context) (*project_v2beta.CreateProjectResponse, context.Context) {
project := inst.CreateProject(ctx, t, inst.DefaultOrg.GetId(), integration.ProjectName(), false, false)
userResp := inst.CreateMachineUser(ctx)
patResp := inst.CreatePersonalAccessToken(ctx, userResp.GetUserId())
inst.CreateProjectMembership(t, ctx, project.GetId(), userResp.GetUserId())
projectOwnerCtx := integration.WithAuthorizationToken(context.Background(), patResp.Token)
return project, projectOwnerCtx
}
func samlMetadataGen(entityID string) []byte {
str := fmt.Sprintf(`<?xml version="1.0"?>
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
validUntil="2022-08-26T14:08:16Z"
cacheDuration="PT604800S"
entityID="%s">
<md:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
Location="https://test.com/saml/acs"
index="1" />
</md:SPSSODescriptor>
</md:EntityDescriptor>
`,
entityID)
return []byte(str)
}
func createSAMLAppWithName(t *testing.T, baseURI, projectID string) ([]byte, *application.CreateApplicationResponse, string) {
samlMetas := samlMetadataGen(integration.URL())
appName := integration.ApplicationName()
appForSAMLConfigChange, appSAMLConfigChangeErr := instance.Client.ApplicationV2.CreateApplication(IAMOwnerCtx, &application.CreateApplicationRequest{
ProjectId: projectID,
Name: appName,
ApplicationType: &application.CreateApplicationRequest_SamlConfiguration{
SamlConfiguration: &application.CreateSAMLApplicationRequest{
Metadata: &application.CreateSAMLApplicationRequest_MetadataXml{
MetadataXml: samlMetas,
},
LoginVersion: &application.LoginVersion{
Version: &application.LoginVersion_LoginV2{
LoginV2: &application.LoginV2{
BaseUri: &baseURI,
},
},
},
},
},
})
require.Nil(t, appSAMLConfigChangeErr)
return samlMetas, appForSAMLConfigChange, appName
}
func createSAMLApp(t *testing.T, baseURI, projectID string) ([]byte, *application.CreateApplicationResponse) {
metas, app, _ := createSAMLAppWithName(t, baseURI, projectID)
return metas, app
}
func createOIDCAppWithName(t *testing.T, baseURI, projectID string) (*application.CreateApplicationResponse, string) {
appName := integration.ApplicationName()
appForOIDCConfigChange, appOIDCConfigChangeErr := instance.Client.ApplicationV2.CreateApplication(IAMOwnerCtx, &application.CreateApplicationRequest{
ProjectId: projectID,
Name: appName,
ApplicationType: &application.CreateApplicationRequest_OidcConfiguration{
OidcConfiguration: &application.CreateOIDCApplicationRequest{
RedirectUris: []string{"http://example.com"},
ResponseTypes: []application.OIDCResponseType{application.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE},
GrantTypes: []application.OIDCGrantType{application.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE},
ApplicationType: application.OIDCApplicationType_OIDC_APP_TYPE_WEB,
AuthMethodType: application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC,
PostLogoutRedirectUris: []string{"http://example.com/home"},
Version: application.OIDCVersion_OIDC_VERSION_1_0,
AccessTokenType: application.OIDCTokenType_OIDC_TOKEN_TYPE_JWT,
BackChannelLogoutUri: "http://example.com/logout",
LoginVersion: &application.LoginVersion{
Version: &application.LoginVersion_LoginV2{
LoginV2: &application.LoginV2{
BaseUri: &baseURI,
},
},
},
},
},
})
require.Nil(t, appOIDCConfigChangeErr)
return appForOIDCConfigChange, appName
}
func createOIDCApp(t *testing.T, baseURI, projctID string) *application.CreateApplicationResponse {
app, _ := createOIDCAppWithName(t, baseURI, projctID)
return app
}
func createAPIAppWithName(t *testing.T, ctx context.Context, inst *integration.Instance, projectID string) (*application.CreateApplicationResponse, string) {
appName := integration.ApplicationName()
reqForAPIAppCreation := &application.CreateApplicationRequest_ApiConfiguration{
ApiConfiguration: &application.CreateAPIApplicationRequest{AuthMethodType: application.APIAuthMethodType_API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT},
}
appForAPIConfigChange, appAPIConfigChangeErr := inst.Client.ApplicationV2.CreateApplication(ctx, &application.CreateApplicationRequest{
ProjectId: projectID,
Name: appName,
ApplicationType: reqForAPIAppCreation,
})
require.Nil(t, appAPIConfigChangeErr)
return appForAPIConfigChange, appName
}
func createAPIApp(t *testing.T, ctx context.Context, inst *integration.Instance, projectID string) *application.CreateApplicationResponse {
res, _ := createAPIAppWithName(t, ctx, inst, projectID)
return res
}
func deactivateApp(t *testing.T, appToDeactivate *application.CreateApplicationResponse, projectID string) {
_, appDeactivateErr := instance.Client.ApplicationV2.DeactivateApplication(IAMOwnerCtx, &application.DeactivateApplicationRequest{
ProjectId: projectID,
ApplicationId: appToDeactivate.GetApplicationId(),
})
require.Nil(t, appDeactivateErr)
}
func ensureFeaturePermissionV2Enabled(t *testing.T, instance *integration.Instance) {
ctx := instance.WithAuthorization(context.Background(), integration.UserTypeIAMOwner)
f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{
Inheritance: true,
})
require.NoError(t, err)
if f.PermissionCheckV2.GetEnabled() {
return
}
_, err = instance.Client.FeatureV2.SetInstanceFeatures(ctx, &feature.SetInstanceFeaturesRequest{
PermissionCheckV2: gu.Ptr(true),
})
require.NoError(t, err)
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(ctx, 5*time.Minute)
require.EventuallyWithT(t, func(tt *assert.CollectT) {
f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{Inheritance: true})
require.NoError(tt, err)
assert.True(tt, f.PermissionCheckV2.GetEnabled())
}, retryDuration, tick, "timed out waiting for ensuring instance feature")
}
func createAppKey(t *testing.T, ctx context.Context, inst *integration.Instance, projectID, appID string, expirationDate time.Time) *application.CreateApplicationKeyResponse {
res, err := inst.Client.ApplicationV2.CreateApplicationKey(ctx,
&application.CreateApplicationKeyRequest{
ApplicationId: appID,
ProjectId: projectID,
ExpirationDate: timestamppb.New(expirationDate.UTC()),
},
)
require.Nil(t, err)
return res
}