mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 13:19:21 +00:00
feat: App Keys API v2 (#10140)
# Which Problems Are Solved This PR *partially* addresses #9450 . Specifically, it implements the resource based API for app keys. This PR, together with https://github.com/zitadel/zitadel/pull/10077 completes #9450 . # How the Problems Are Solved - Implementation of the following endpoints: `CreateApplicationKey`, `DeleteApplicationKey`, `GetApplicationKey`, `ListApplicationKeys` - `ListApplicationKeys` can filter by project, app or organization ID. Sorting is also possible according to some criteria. - All endpoints use permissions V2 # TODO - [x] Deprecate old endpoints # Additional Context Closes #9450
This commit is contained in:
47
internal/api/grpc/app/v2beta/app_key.go
Normal file
47
internal/api/grpc/app/v2beta/app_key.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/app/v2beta/convert"
|
||||
app "github.com/zitadel/zitadel/pkg/grpc/app/v2beta"
|
||||
)
|
||||
|
||||
func (s *Server) CreateApplicationKey(ctx context.Context, req *app.CreateApplicationKeyRequest) (*app.CreateApplicationKeyResponse, error) {
|
||||
domainReq := convert.CreateAPIClientKeyRequestToDomain(req)
|
||||
|
||||
appKey, err := s.command.AddApplicationKey(ctx, domainReq, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyDetails, err := appKey.Detail()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &app.CreateApplicationKeyResponse{
|
||||
Id: appKey.KeyID,
|
||||
CreationDate: timestamppb.New(appKey.ChangeDate),
|
||||
KeyDetails: keyDetails,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) DeleteApplicationKey(ctx context.Context, req *app.DeleteApplicationKeyRequest) (*app.DeleteApplicationKeyResponse, error) {
|
||||
deletionDetails, err := s.command.RemoveApplicationKey(ctx,
|
||||
strings.TrimSpace(req.GetProjectId()),
|
||||
strings.TrimSpace(req.GetApplicationId()),
|
||||
strings.TrimSpace(req.GetId()),
|
||||
strings.TrimSpace(req.GetOrganizationId()),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &app.DeleteApplicationKeyResponse{
|
||||
DeletionDate: timestamppb.New(deletionDetails.EventDate),
|
||||
}, nil
|
||||
}
|
@@ -1,6 +1,8 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
@@ -58,3 +60,39 @@ func apiAuthMethodTypeToPb(methodType domain.APIAuthMethodType) app.APIAuthMetho
|
||||
return app.APIAuthMethodType_API_AUTH_METHOD_TYPE_BASIC
|
||||
}
|
||||
}
|
||||
|
||||
func GetApplicationKeyQueriesRequestToDomain(orgID, projectID, appID string) ([]query.SearchQuery, error) {
|
||||
var searchQueries []query.SearchQuery
|
||||
|
||||
orgID, projectID, appID = strings.TrimSpace(orgID), strings.TrimSpace(projectID), strings.TrimSpace(appID)
|
||||
|
||||
if orgID != "" {
|
||||
resourceOwner, err := query.NewAuthNKeyResourceOwnerQuery(orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
searchQueries = append(searchQueries, resourceOwner)
|
||||
}
|
||||
|
||||
if projectID != "" {
|
||||
aggregateID, err := query.NewAuthNKeyAggregateIDQuery(projectID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
searchQueries = append(searchQueries, aggregateID)
|
||||
}
|
||||
|
||||
if appID != "" {
|
||||
objectID, err := query.NewAuthNKeyObjectIDQuery(appID)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
searchQueries = append(searchQueries, objectID)
|
||||
}
|
||||
|
||||
return searchQueries, nil
|
||||
}
|
||||
|
@@ -4,6 +4,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
@@ -147,3 +148,71 @@ func Test_apiAuthMethodTypeToPb(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
func TestGetApplicationKeyQueriesRequestToDomain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputOrgID string
|
||||
inputProjectID string
|
||||
inputAppID string
|
||||
|
||||
expectedQueriesLength int
|
||||
}{
|
||||
{
|
||||
testName: "all IDs provided",
|
||||
inputOrgID: "org-1",
|
||||
inputProjectID: "proj-1",
|
||||
inputAppID: "app-1",
|
||||
expectedQueriesLength: 3,
|
||||
},
|
||||
{
|
||||
testName: "only org ID",
|
||||
inputOrgID: "org-1",
|
||||
inputProjectID: " ",
|
||||
inputAppID: "",
|
||||
expectedQueriesLength: 1,
|
||||
},
|
||||
{
|
||||
testName: "only project ID",
|
||||
inputOrgID: "",
|
||||
inputProjectID: "proj-1",
|
||||
inputAppID: " ",
|
||||
expectedQueriesLength: 1,
|
||||
},
|
||||
{
|
||||
testName: "only app ID",
|
||||
inputOrgID: " ",
|
||||
inputProjectID: "",
|
||||
inputAppID: "app-1",
|
||||
expectedQueriesLength: 1,
|
||||
},
|
||||
{
|
||||
testName: "empty IDs",
|
||||
inputOrgID: " ",
|
||||
inputProjectID: " ",
|
||||
inputAppID: " ",
|
||||
expectedQueriesLength: 0,
|
||||
},
|
||||
{
|
||||
testName: "with spaces",
|
||||
inputOrgID: " org-1 ",
|
||||
inputProjectID: " proj-1 ",
|
||||
inputAppID: " app-1 ",
|
||||
expectedQueriesLength: 3,
|
||||
},
|
||||
}
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// When
|
||||
got, err := GetApplicationKeyQueriesRequestToDomain(tc.inputOrgID, tc.inputProjectID, tc.inputAppID)
|
||||
|
||||
// Then
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Len(t, got, tc.expectedQueriesLength)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@ package convert
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/filter/v2"
|
||||
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
app "github.com/zitadel/zitadel/pkg/grpc/app/v2beta"
|
||||
@@ -163,3 +165,98 @@ func appQueryToModel(appQuery *app.ApplicationSearchFilter) (query.SearchQuery,
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "CONV-z2mAGy", "List.Query.Invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func CreateAPIClientKeyRequestToDomain(key *app.CreateApplicationKeyRequest) *domain.ApplicationKey {
|
||||
return &domain.ApplicationKey{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: strings.TrimSpace(key.GetProjectId()),
|
||||
},
|
||||
ExpirationDate: key.GetExpirationDate().AsTime(),
|
||||
Type: domain.AuthNKeyTypeJSON,
|
||||
ApplicationID: strings.TrimSpace(key.GetAppId()),
|
||||
}
|
||||
}
|
||||
|
||||
func ListApplicationKeysRequestToDomain(sysDefaults systemdefaults.SystemDefaults, req *app.ListApplicationKeysRequest) (*query.AuthNKeySearchQueries, error) {
|
||||
var queries []query.SearchQuery
|
||||
|
||||
switch req.GetResourceId().(type) {
|
||||
case *app.ListApplicationKeysRequest_ApplicationId:
|
||||
object, err := query.NewAuthNKeyObjectIDQuery(strings.TrimSpace(req.GetApplicationId()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries = append(queries, object)
|
||||
case *app.ListApplicationKeysRequest_OrganizationId:
|
||||
resourceOwner, err := query.NewAuthNKeyResourceOwnerQuery(strings.TrimSpace(req.GetOrganizationId()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries = append(queries, resourceOwner)
|
||||
case *app.ListApplicationKeysRequest_ProjectId:
|
||||
aggregate, err := query.NewAuthNKeyAggregateIDQuery(strings.TrimSpace(req.GetProjectId()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries = append(queries, aggregate)
|
||||
case nil:
|
||||
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "CONV-t3ENme", "unexpected resource id")
|
||||
}
|
||||
|
||||
offset, limit, asc, err := filter.PaginationPbToQuery(sysDefaults, req.GetPagination())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &query.AuthNKeySearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
SortingColumn: appKeysSortingToColumn(req.GetSortingColumn()),
|
||||
},
|
||||
|
||||
Queries: queries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func appKeysSortingToColumn(sortingCriteria app.ApplicationKeysSorting) query.Column {
|
||||
switch sortingCriteria {
|
||||
case app.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_PROJECT_ID:
|
||||
return query.AuthNKeyColumnAggregateID
|
||||
case app.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_CREATION_DATE:
|
||||
return query.AuthNKeyColumnCreationDate
|
||||
case app.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_EXPIRATION:
|
||||
return query.AuthNKeyColumnExpiration
|
||||
case app.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_ORGANIZATION_ID:
|
||||
return query.AuthNKeyColumnResourceOwner
|
||||
case app.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_TYPE:
|
||||
return query.AuthNKeyColumnType
|
||||
case app.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_APPLICATION_ID:
|
||||
return query.AuthNKeyColumnObjectID
|
||||
case app.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_ID:
|
||||
fallthrough
|
||||
default:
|
||||
return query.AuthNKeyColumnID
|
||||
}
|
||||
}
|
||||
|
||||
func ApplicationKeysToPb(keys []*query.AuthNKey) []*app.ApplicationKey {
|
||||
pbAppKeys := make([]*app.ApplicationKey, len(keys))
|
||||
|
||||
for i, k := range keys {
|
||||
pbKey := &app.ApplicationKey{
|
||||
Id: k.ID,
|
||||
ApplicationId: k.ApplicationID,
|
||||
ProjectId: k.AggregateID,
|
||||
CreationDate: timestamppb.New(k.CreationDate),
|
||||
OrganizationId: k.ResourceOwner,
|
||||
ExpirationDate: timestamppb.New(k.Expiration),
|
||||
}
|
||||
pbAppKeys[i] = pbKey
|
||||
}
|
||||
|
||||
return pbAppKeys
|
||||
}
|
||||
|
@@ -518,3 +518,187 @@ func TestAppQueryToModel(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListApplicationKeysRequestToDomain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
resourceOwnerQuery, err := query.NewAuthNKeyResourceOwnerQuery("org1")
|
||||
require.NoError(t, err)
|
||||
|
||||
projectIDQuery, err := query.NewAuthNKeyAggregateIDQuery("project1")
|
||||
require.NoError(t, err)
|
||||
|
||||
appIDQuery, err := query.NewAuthNKeyObjectIDQuery("app1")
|
||||
require.NoError(t, err)
|
||||
|
||||
sysDefaults := systemdefaults.SystemDefaults{DefaultQueryLimit: 100, MaxQueryLimit: 150}
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
req *app.ListApplicationKeysRequest
|
||||
|
||||
expectedResult *query.AuthNKeySearchQueries
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "invalid pagination limit",
|
||||
req: &app.ListApplicationKeysRequest{
|
||||
Pagination: &filter_pb_v2.PaginationRequest{Asc: true, Limit: uint32(sysDefaults.MaxQueryLimit + 1)},
|
||||
},
|
||||
expectedResult: nil,
|
||||
expectedError: zerrors.ThrowInvalidArgumentf(fmt.Errorf("given: %d, allowed: %d", sysDefaults.MaxQueryLimit+1, sysDefaults.MaxQueryLimit), "QUERY-4M0fs", "Errors.Query.LimitExceeded"),
|
||||
},
|
||||
{
|
||||
name: "empty request",
|
||||
req: &app.ListApplicationKeysRequest{
|
||||
Pagination: &filter_pb_v2.PaginationRequest{Asc: true},
|
||||
},
|
||||
expectedResult: &query.AuthNKeySearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
Asc: true,
|
||||
SortingColumn: query.AuthNKeyColumnID,
|
||||
},
|
||||
Queries: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "only organization id",
|
||||
req: &app.ListApplicationKeysRequest{
|
||||
ResourceId: &app.ListApplicationKeysRequest_OrganizationId{OrganizationId: "org1"},
|
||||
Pagination: &filter_pb_v2.PaginationRequest{Asc: true},
|
||||
},
|
||||
expectedResult: &query.AuthNKeySearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
Asc: true,
|
||||
SortingColumn: query.AuthNKeyColumnID,
|
||||
},
|
||||
Queries: []query.SearchQuery{
|
||||
resourceOwnerQuery,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "only project id",
|
||||
req: &app.ListApplicationKeysRequest{
|
||||
ResourceId: &app.ListApplicationKeysRequest_ProjectId{ProjectId: "project1"},
|
||||
Pagination: &filter_pb_v2.PaginationRequest{Asc: true},
|
||||
},
|
||||
expectedResult: &query.AuthNKeySearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
Asc: true,
|
||||
SortingColumn: query.AuthNKeyColumnID,
|
||||
},
|
||||
Queries: []query.SearchQuery{
|
||||
projectIDQuery,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "only application id",
|
||||
req: &app.ListApplicationKeysRequest{
|
||||
ResourceId: &app.ListApplicationKeysRequest_ApplicationId{ApplicationId: "app1"},
|
||||
Pagination: &filter_pb_v2.PaginationRequest{Asc: true},
|
||||
},
|
||||
expectedResult: &query.AuthNKeySearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
Asc: true,
|
||||
SortingColumn: query.AuthNKeyColumnID,
|
||||
},
|
||||
Queries: []query.SearchQuery{
|
||||
appIDQuery,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
result, err := ListApplicationKeysRequestToDomain(sysDefaults, tc.req)
|
||||
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
assert.Equal(t, tc.expectedResult, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplicationKeysToPb(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now()
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
input []*query.AuthNKey
|
||||
expected []*app.ApplicationKey
|
||||
}{
|
||||
{
|
||||
name: "multiple keys",
|
||||
input: []*query.AuthNKey{
|
||||
{
|
||||
ID: "key1",
|
||||
AggregateID: "project1",
|
||||
ApplicationID: "app1",
|
||||
CreationDate: now,
|
||||
ResourceOwner: "org1",
|
||||
Expiration: now.Add(24 * time.Hour),
|
||||
Type: domain.AuthNKeyTypeJSON,
|
||||
},
|
||||
{
|
||||
ID: "key2",
|
||||
AggregateID: "project2",
|
||||
ApplicationID: "app1",
|
||||
CreationDate: now.Add(-time.Hour),
|
||||
ResourceOwner: "org2",
|
||||
Expiration: now.Add(48 * time.Hour),
|
||||
Type: domain.AuthNKeyTypeNONE,
|
||||
},
|
||||
},
|
||||
expected: []*app.ApplicationKey{
|
||||
{
|
||||
Id: "key1",
|
||||
ApplicationId: "app1",
|
||||
ProjectId: "project1",
|
||||
CreationDate: timestamppb.New(now),
|
||||
OrganizationId: "org1",
|
||||
ExpirationDate: timestamppb.New(now.Add(24 * time.Hour)),
|
||||
},
|
||||
{
|
||||
Id: "key2",
|
||||
ApplicationId: "app1",
|
||||
ProjectId: "project2",
|
||||
CreationDate: timestamppb.New(now.Add(-time.Hour)),
|
||||
OrganizationId: "org2",
|
||||
ExpirationDate: timestamppb.New(now.Add(48 * time.Hour)),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty slice",
|
||||
input: []*query.AuthNKey{},
|
||||
expected: []*app.ApplicationKey{},
|
||||
},
|
||||
{
|
||||
name: "nil input",
|
||||
input: nil,
|
||||
expected: []*app.ApplicationKey{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
result := ApplicationKeysToPb(tc.input)
|
||||
assert.Equal(t, tc.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
206
internal/api/grpc/app/v2beta/integration_test/app_key_test.go
Normal file
206
internal/api/grpc/app/v2beta/integration_test/app_key_test.go
Normal file
@@ -0,0 +1,206 @@
|
||||
//go:build integration
|
||||
|
||||
package app_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"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"
|
||||
|
||||
app "github.com/zitadel/zitadel/pkg/grpc/app/v2beta"
|
||||
)
|
||||
|
||||
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 *app.CreateApplicationKeyRequest
|
||||
inputCtx context.Context
|
||||
|
||||
expectedErrorType codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when app id is not found should return failed precondition",
|
||||
inputCtx: IAMOwnerCtx,
|
||||
creationRequest: &app.CreateApplicationKeyRequest{
|
||||
ProjectId: p.GetId(),
|
||||
AppId: gofakeit.UUID(),
|
||||
ExpirationDate: timestamppb.New(time.Now().AddDate(0, 0, 1).UTC()),
|
||||
},
|
||||
expectedErrorType: codes.FailedPrecondition,
|
||||
},
|
||||
{
|
||||
testName: "when CreateAPIApp request is valid should create app and return no error",
|
||||
inputCtx: IAMOwnerCtx,
|
||||
creationRequest: &app.CreateApplicationKeyRequest{
|
||||
ProjectId: p.GetId(),
|
||||
AppId: createdApp.GetAppId(),
|
||||
ExpirationDate: timestamppb.New(time.Now().AddDate(0, 0, 1).UTC()),
|
||||
},
|
||||
},
|
||||
|
||||
// LoginUser
|
||||
{
|
||||
testName: "when user has no project.app.write permission for app key generation should return permission error",
|
||||
inputCtx: LoginUserCtx,
|
||||
creationRequest: &app.CreateApplicationKeyRequest{
|
||||
ProjectId: p.GetId(),
|
||||
AppId: createdApp.GetAppId(),
|
||||
ExpirationDate: timestamppb.New(time.Now().AddDate(0, 0, 1).UTC()),
|
||||
},
|
||||
expectedErrorType: codes.PermissionDenied,
|
||||
},
|
||||
|
||||
// OrgOwner
|
||||
{
|
||||
testName: "when user is OrgOwner app key request should succeed",
|
||||
inputCtx: OrgOwnerCtx,
|
||||
creationRequest: &app.CreateApplicationKeyRequest{
|
||||
ProjectId: p.GetId(),
|
||||
AppId: createdApp.GetAppId(),
|
||||
ExpirationDate: timestamppb.New(time.Now().AddDate(0, 0, 1).UTC()),
|
||||
},
|
||||
},
|
||||
|
||||
// ProjectOwner
|
||||
{
|
||||
testName: "when user is ProjectOwner app key request should succeed",
|
||||
inputCtx: projectOwnerCtx,
|
||||
creationRequest: &app.CreateApplicationKeyRequest{
|
||||
ProjectId: p.GetId(),
|
||||
AppId: createdApp.GetAppId(),
|
||||
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.AppV2Beta.CreateApplicationKey(tc.inputCtx, tc.creationRequest)
|
||||
|
||||
require.Equal(t, tc.expectedErrorType, status.Code(err))
|
||||
if tc.expectedErrorType == codes.OK {
|
||||
assert.NotZero(t, res.GetId())
|
||||
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) *app.DeleteApplicationKeyRequest
|
||||
inputCtx context.Context
|
||||
|
||||
expectedErrorType codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when app key ID is not found should return not found error",
|
||||
inputCtx: IAMOwnerCtx,
|
||||
deletionRequest: func(ttt *testing.T) *app.DeleteApplicationKeyRequest {
|
||||
return &app.DeleteApplicationKeyRequest{
|
||||
Id: gofakeit.UUID(),
|
||||
ProjectId: p.GetId(),
|
||||
ApplicationId: createdApp.GetAppId(),
|
||||
}
|
||||
},
|
||||
expectedErrorType: codes.NotFound,
|
||||
},
|
||||
{
|
||||
testName: "when valid app key ID should delete successfully",
|
||||
inputCtx: IAMOwnerCtx,
|
||||
deletionRequest: func(ttt *testing.T) *app.DeleteApplicationKeyRequest {
|
||||
createdAppKey := createAppKey(ttt, IAMOwnerCtx, instance, p.GetId(), createdApp.GetAppId(), time.Now().AddDate(0, 0, 1))
|
||||
|
||||
return &app.DeleteApplicationKeyRequest{
|
||||
Id: createdAppKey.GetId(),
|
||||
ProjectId: p.GetId(),
|
||||
ApplicationId: createdApp.GetAppId(),
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// LoginUser
|
||||
{
|
||||
testName: "when user has no project.app.write permission for app key deletion should return permission error",
|
||||
inputCtx: LoginUserCtx,
|
||||
deletionRequest: func(ttt *testing.T) *app.DeleteApplicationKeyRequest {
|
||||
createdAppKey := createAppKey(ttt, IAMOwnerCtx, instance, p.GetId(), createdApp.GetAppId(), time.Now().AddDate(0, 0, 1))
|
||||
|
||||
return &app.DeleteApplicationKeyRequest{
|
||||
Id: createdAppKey.GetId(),
|
||||
ProjectId: p.GetId(),
|
||||
ApplicationId: createdApp.GetAppId(),
|
||||
}
|
||||
},
|
||||
expectedErrorType: codes.PermissionDenied,
|
||||
},
|
||||
|
||||
// ProjectOwner
|
||||
{
|
||||
testName: "when user is OrgOwner API request should succeed",
|
||||
inputCtx: projectOwnerCtx,
|
||||
deletionRequest: func(ttt *testing.T) *app.DeleteApplicationKeyRequest {
|
||||
createdAppKey := createAppKey(ttt, IAMOwnerCtx, instance, p.GetId(), createdApp.GetAppId(), time.Now().AddDate(0, 0, 1))
|
||||
|
||||
return &app.DeleteApplicationKeyRequest{
|
||||
Id: createdAppKey.GetId(),
|
||||
ProjectId: p.GetId(),
|
||||
ApplicationId: createdApp.GetAppId(),
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// OrganizationOwner
|
||||
{
|
||||
testName: "when user is OrgOwner app key deletion request should succeed",
|
||||
inputCtx: OrgOwnerCtx,
|
||||
deletionRequest: func(ttt *testing.T) *app.DeleteApplicationKeyRequest {
|
||||
createdAppKey := createAppKey(ttt, IAMOwnerCtx, instance, p.GetId(), createdApp.GetAppId(), time.Now().AddDate(0, 0, 1))
|
||||
|
||||
return &app.DeleteApplicationKeyRequest{
|
||||
Id: createdAppKey.GetId(),
|
||||
ProjectId: p.GetId(),
|
||||
ApplicationId: createdApp.GetAppId(),
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Given
|
||||
deletionReq := tc.deletionRequest(t)
|
||||
|
||||
// When
|
||||
res, err := instance.Client.AppV2Beta.DeleteApplicationKey(tc.inputCtx, deletionReq)
|
||||
|
||||
// Then
|
||||
require.Equal(t, tc.expectedErrorType, status.Code(err))
|
||||
if tc.expectedErrorType == codes.OK {
|
||||
assert.NotEmpty(t, res.GetDeletionDate())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
//go:build integration
|
||||
|
||||
package instance_test
|
||||
package app_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -653,9 +653,9 @@ func TestUpdateApplication_WithDifferentPermissions(t *testing.T) {
|
||||
})
|
||||
require.Nil(t, appNameChangeErr)
|
||||
|
||||
appForAPIConfigChangeForProjectOwner := createAPIApp(t, p.GetId())
|
||||
appForAPIConfigChangeForOrgOwner := createAPIApp(t, p.GetId())
|
||||
appForAPIConfigChangeForLoginUser := createAPIApp(t, p.GetId())
|
||||
appForAPIConfigChangeForProjectOwner := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
|
||||
appForAPIConfigChangeForOrgOwner := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
|
||||
appForAPIConfigChangeForLoginUser := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
|
||||
|
||||
appForOIDCConfigChangeForProjectOwner := createOIDCApp(t, baseURI, p.GetId())
|
||||
appForOIDCConfigChangeForOrgOwner := createOIDCApp(t, baseURI, p.GetId())
|
||||
@@ -914,9 +914,9 @@ func TestDeleteApplication(t *testing.T) {
|
||||
func TestDeleteApplication_WithDifferentPermissions(t *testing.T) {
|
||||
p, projectOwnerCtx := getProjectAndProjectContext(t, instance, IAMOwnerCtx)
|
||||
|
||||
appToDeleteForLoginUser := createAPIApp(t, p.GetId())
|
||||
appToDeleteForProjectOwner := createAPIApp(t, p.GetId())
|
||||
appToDeleteForOrgOwner := createAPIApp(t, p.GetId())
|
||||
appToDeleteForLoginUser := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
|
||||
appToDeleteForProjectOwner := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
|
||||
appToDeleteForOrgOwner := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
|
||||
|
||||
t.Parallel()
|
||||
tt := []struct {
|
||||
@@ -1035,9 +1035,9 @@ func TestDeactivateApplication(t *testing.T) {
|
||||
func TestDeactivateApplication_WithDifferentPermissions(t *testing.T) {
|
||||
p, projectOwnerCtx := getProjectAndProjectContext(t, instance, IAMOwnerCtx)
|
||||
|
||||
appToDeactivateForLoginUser := createAPIApp(t, p.GetId())
|
||||
appToDeactivateForPrjectOwner := createAPIApp(t, p.GetId())
|
||||
appToDeactivateForOrgOwner := createAPIApp(t, p.GetId())
|
||||
appToDeactivateForLoginUser := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
|
||||
appToDeactivateForPrjectOwner := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
|
||||
appToDeactivateForOrgOwner := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
|
||||
|
||||
t.Parallel()
|
||||
|
||||
@@ -1162,13 +1162,13 @@ func TestReactivateApplication(t *testing.T) {
|
||||
func TestReactivateApplication_WithDifferentPermissions(t *testing.T) {
|
||||
p, projectOwnerCtx := getProjectAndProjectContext(t, instance, IAMOwnerCtx)
|
||||
|
||||
appToReactivateForLoginUser := createAPIApp(t, p.GetId())
|
||||
appToReactivateForLoginUser := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
|
||||
deactivateApp(t, appToReactivateForLoginUser, p.GetId())
|
||||
|
||||
appToReactivateForProjectOwner := createAPIApp(t, p.GetId())
|
||||
appToReactivateForProjectOwner := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
|
||||
deactivateApp(t, appToReactivateForProjectOwner, p.GetId())
|
||||
|
||||
appToReactivateForOrgOwner := createAPIApp(t, p.GetId())
|
||||
appToReactivateForOrgOwner := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
|
||||
deactivateApp(t, appToReactivateForOrgOwner, p.GetId())
|
||||
|
||||
t.Parallel()
|
||||
@@ -1342,9 +1342,9 @@ func TestRegenerateClientSecret(t *testing.T) {
|
||||
func TestRegenerateClientSecret_WithDifferentPermissions(t *testing.T) {
|
||||
p, projectOwnerCtx := getProjectAndProjectContext(t, instance, IAMOwnerCtx)
|
||||
|
||||
apiAppToRegenForLoginUser := createAPIApp(t, p.GetId())
|
||||
apiAppToRegenForProjectOwner := createAPIApp(t, p.GetId())
|
||||
apiAppToRegenForOrgOwner := createAPIApp(t, p.GetId())
|
||||
apiAppToRegenForLoginUser := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
|
||||
apiAppToRegenForProjectOwner := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
|
||||
apiAppToRegenForOrgOwner := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
|
||||
|
||||
oidcAppToRegenForLoginUser := createOIDCApp(t, baseURI, p.GetId())
|
||||
oidcAppToRegenForProjectOwner := createOIDCApp(t, baseURI, p.GetId())
|
||||
|
@@ -1,6 +1,6 @@
|
||||
//go:build integration
|
||||
|
||||
package instance_test
|
||||
package app_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -165,9 +165,9 @@ func TestListApplications(t *testing.T) {
|
||||
|
||||
t.Parallel()
|
||||
|
||||
createdApiApp, apiAppName := createAPIAppWithName(t, p.GetId())
|
||||
createdApiApp, apiAppName := createAPIAppWithName(t, IAMOwnerCtx, instance, p.GetId())
|
||||
|
||||
createdDeactivatedApiApp, deactivatedApiAppName := createAPIAppWithName(t, p.GetId())
|
||||
createdDeactivatedApiApp, deactivatedApiAppName := createAPIAppWithName(t, IAMOwnerCtx, instance, p.GetId())
|
||||
deactivateApp(t, createdDeactivatedApiApp, p.GetId())
|
||||
|
||||
_, createdSAMLApp, samlAppName := createSAMLAppWithName(t, gofakeit.URL(), p.GetId())
|
||||
@@ -573,3 +573,248 @@ func TestListApplications_WithPermissionV2(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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.GetAppId(), time.Now().AddDate(0, 0, 1))
|
||||
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *app.GetApplicationKeyRequest
|
||||
inputCtx context.Context
|
||||
|
||||
expectedErrorType codes.Code
|
||||
expectedAppKeyID string
|
||||
}{
|
||||
{
|
||||
testName: "when unknown app ID should return not found error",
|
||||
inputCtx: IAMOwnerCtx,
|
||||
inputRequest: &app.GetApplicationKeyRequest{
|
||||
Id: gofakeit.Sentence(2),
|
||||
},
|
||||
|
||||
expectedErrorType: codes.NotFound,
|
||||
},
|
||||
{
|
||||
testName: "when user has no permission should return membership not found error",
|
||||
inputCtx: NoPermissionCtx,
|
||||
inputRequest: &app.GetApplicationKeyRequest{
|
||||
Id: createdAppKey.GetId(),
|
||||
},
|
||||
|
||||
expectedErrorType: codes.NotFound,
|
||||
},
|
||||
{
|
||||
testName: "when providing API app ID should return valid API app result",
|
||||
inputCtx: projectOwnerCtx,
|
||||
inputRequest: &app.GetApplicationKeyRequest{
|
||||
Id: createdAppKey.GetId(),
|
||||
},
|
||||
|
||||
expectedAppKeyID: createdAppKey.GetId(),
|
||||
},
|
||||
{
|
||||
testName: "when user is OrgOwner should return request key",
|
||||
inputCtx: OrgOwnerCtx,
|
||||
inputRequest: &app.GetApplicationKeyRequest{
|
||||
Id: createdAppKey.GetId(),
|
||||
ProjectId: p.GetId(),
|
||||
},
|
||||
|
||||
expectedAppKeyID: createdAppKey.GetId(),
|
||||
},
|
||||
{
|
||||
testName: "when user is IAMOwner should return request key",
|
||||
inputCtx: OrgOwnerCtx,
|
||||
inputRequest: &app.GetApplicationKeyRequest{
|
||||
Id: createdAppKey.GetId(),
|
||||
OrganizationId: instance.DefaultOrg.GetId(),
|
||||
},
|
||||
|
||||
expectedAppKeyID: createdAppKey.GetId(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputCtx, 30*time.Second)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
// When
|
||||
res, err := instance.Client.AppV2Beta.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.GetId())
|
||||
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.GetAppId(), in2Days)
|
||||
appKey2 := createAppKey(t, IAMOwnerCtx, instance, p.GetId(), createdApiApp1.GetAppId(), in3Days)
|
||||
appKey3 := createAppKey(t, IAMOwnerCtx, instance, p.GetId(), createdApiApp1.GetAppId(), tomorrow)
|
||||
appKey4 := createAppKey(t, IAMOwnerCtx, instance, p.GetId(), createdApiApp2.GetAppId(), tomorrow)
|
||||
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *app.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: &app.ListApplicationKeysRequest{
|
||||
ResourceId: &app.ListApplicationKeysRequest_ProjectId{ProjectId: p.GetId()},
|
||||
Pagination: &filter.PaginationRequest{Asc: true},
|
||||
SortingColumn: app.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_EXPIRATION,
|
||||
},
|
||||
expectedAppKeysIDs: []string{appKey3.GetId(), appKey4.GetId(), appKey1.GetId(), appKey2.GetId()},
|
||||
},
|
||||
{
|
||||
testName: "when sorting by creation date should return keys sorted by creation date descending",
|
||||
inputCtx: IAMOwnerCtx,
|
||||
inputRequest: &app.ListApplicationKeysRequest{
|
||||
ResourceId: &app.ListApplicationKeysRequest_ProjectId{ProjectId: p.GetId()},
|
||||
SortingColumn: app.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_CREATION_DATE,
|
||||
},
|
||||
expectedAppKeysIDs: []string{appKey4.GetId(), appKey3.GetId(), appKey2.GetId(), appKey1.GetId()},
|
||||
},
|
||||
{
|
||||
testName: "when filtering by app ID should return keys matching app ID sorted by ID",
|
||||
inputCtx: projectOwnerCtx,
|
||||
inputRequest: &app.ListApplicationKeysRequest{
|
||||
Pagination: &filter.PaginationRequest{Asc: true},
|
||||
ResourceId: &app.ListApplicationKeysRequest_ApplicationId{ApplicationId: createdApiApp1.GetAppId()},
|
||||
},
|
||||
expectedAppKeysIDs: []string{appKey1.GetId(), appKey2.GetId(), appKey3.GetId()},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputCtx, 5*time.Second)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
// When
|
||||
res, err := instance.Client.AppV2Beta.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.GetId())
|
||||
}
|
||||
}
|
||||
}, retryDuration, tick)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListApplicationKeys_WithPermissionV2(t *testing.T) {
|
||||
ensureFeaturePermissionV2Enabled(t, instancePermissionV2)
|
||||
iamOwnerCtx := instancePermissionV2.WithAuthorization(context.Background(), integration.UserTypeIAMOwner)
|
||||
loginUserCtx := instancePermissionV2.WithAuthorization(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.GetAppId(), in2Days)
|
||||
appKey2 := createAppKey(t, iamOwnerCtx, instancePermissionV2, p.GetId(), createdApiApp1.GetAppId(), in3Days)
|
||||
appKey3 := createAppKey(t, iamOwnerCtx, instancePermissionV2, p.GetId(), createdApiApp1.GetAppId(), tomorrow)
|
||||
appKey4 := createAppKey(t, iamOwnerCtx, instancePermissionV2, p.GetId(), createdApiApp2.GetAppId(), tomorrow)
|
||||
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *app.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: &app.ListApplicationKeysRequest{
|
||||
Pagination: &filter.PaginationRequest{Asc: true},
|
||||
SortingColumn: app.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_EXPIRATION,
|
||||
},
|
||||
expectedAppKeysIDs: []string{appKey3.GetId(), appKey4.GetId(), appKey1.GetId(), appKey2.GetId()},
|
||||
},
|
||||
{
|
||||
testName: "when sorting by creation date should return keys sorted by creation date descending",
|
||||
inputCtx: iamOwnerCtx,
|
||||
inputRequest: &app.ListApplicationKeysRequest{
|
||||
SortingColumn: app.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_CREATION_DATE,
|
||||
},
|
||||
expectedAppKeysIDs: []string{appKey4.GetId(), appKey3.GetId(), appKey2.GetId(), appKey1.GetId()},
|
||||
},
|
||||
{
|
||||
testName: "when filtering by app ID should return keys matching app ID sorted by ID",
|
||||
inputCtx: projectOwnerCtx,
|
||||
inputRequest: &app.ListApplicationKeysRequest{
|
||||
Pagination: &filter.PaginationRequest{Asc: true},
|
||||
ResourceId: &app.ListApplicationKeysRequest_ApplicationId{ApplicationId: createdApiApp1.GetAppId()},
|
||||
},
|
||||
expectedAppKeysIDs: []string{appKey1.GetId(), appKey2.GetId(), appKey3.GetId()},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// t.Parallel()
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputCtx, 5*time.Second)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
// When
|
||||
res, err := instancePermissionV2.Client.AppV2Beta.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.GetId())
|
||||
}
|
||||
}
|
||||
}, retryDuration, tick)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
//go:build integration
|
||||
|
||||
package instance_test
|
||||
package app_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"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"
|
||||
app "github.com/zitadel/zitadel/pkg/grpc/app/v2beta"
|
||||
@@ -150,14 +151,14 @@ func createOIDCApp(t *testing.T, baseURI, projctID string) *app.CreateApplicatio
|
||||
return app
|
||||
}
|
||||
|
||||
func createAPIAppWithName(t *testing.T, projectID string) (*app.CreateApplicationResponse, string) {
|
||||
func createAPIAppWithName(t *testing.T, ctx context.Context, inst *integration.Instance, projectID string) (*app.CreateApplicationResponse, string) {
|
||||
appName := gofakeit.AppName()
|
||||
|
||||
reqForAPIAppCreation := &app.CreateApplicationRequest_ApiRequest{
|
||||
ApiRequest: &app.CreateAPIApplicationRequest{AuthMethodType: app.APIAuthMethodType_API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT},
|
||||
}
|
||||
|
||||
appForAPIConfigChange, appAPIConfigChangeErr := instance.Client.AppV2Beta.CreateApplication(IAMOwnerCtx, &app.CreateApplicationRequest{
|
||||
appForAPIConfigChange, appAPIConfigChangeErr := inst.Client.AppV2Beta.CreateApplication(ctx, &app.CreateApplicationRequest{
|
||||
ProjectId: projectID,
|
||||
Name: appName,
|
||||
CreationRequestType: reqForAPIAppCreation,
|
||||
@@ -167,8 +168,8 @@ func createAPIAppWithName(t *testing.T, projectID string) (*app.CreateApplicatio
|
||||
return appForAPIConfigChange, appName
|
||||
}
|
||||
|
||||
func createAPIApp(t *testing.T, projectID string) *app.CreateApplicationResponse {
|
||||
res, _ := createAPIAppWithName(t, projectID)
|
||||
func createAPIApp(t *testing.T, ctx context.Context, inst *integration.Instance, projectID string) *app.CreateApplicationResponse {
|
||||
res, _ := createAPIAppWithName(t, ctx, inst, projectID)
|
||||
return res
|
||||
}
|
||||
|
||||
@@ -203,3 +204,17 @@ func ensureFeaturePermissionV2Enabled(t *testing.T, instance *integration.Instan
|
||||
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) *app.CreateApplicationKeyResponse {
|
||||
res, err := inst.Client.AppV2Beta.CreateApplicationKey(ctx,
|
||||
&app.CreateApplicationKeyRequest{
|
||||
AppId: appID,
|
||||
ProjectId: projectID,
|
||||
ExpirationDate: timestamppb.New(expirationDate.UTC()),
|
||||
},
|
||||
)
|
||||
|
||||
require.Nil(t, err)
|
||||
|
||||
return res
|
||||
}
|
||||
|
@@ -2,9 +2,13 @@ package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/app/v2beta/convert"
|
||||
filter "github.com/zitadel/zitadel/internal/api/grpc/filter/v2"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
app "github.com/zitadel/zitadel/pkg/grpc/app/v2beta"
|
||||
)
|
||||
|
||||
@@ -35,3 +39,38 @@ func (s *Server) ListApplications(ctx context.Context, req *app.ListApplications
|
||||
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, res.SearchResponse),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) GetApplicationKey(ctx context.Context, req *app.GetApplicationKeyRequest) (*app.GetApplicationKeyResponse, error) {
|
||||
queries, err := convert.GetApplicationKeyQueriesRequestToDomain(req.GetOrganizationId(), req.GetProjectId(), req.GetApplicationId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
key, err := s.query.GetAuthNKeyByIDWithPermission(ctx, true, strings.TrimSpace(req.GetId()), s.checkPermission, queries...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &app.GetApplicationKeyResponse{
|
||||
Id: key.ID,
|
||||
CreationDate: timestamppb.New(key.CreationDate),
|
||||
ExpirationDate: timestamppb.New(key.Expiration),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ListApplicationKeys(ctx context.Context, req *app.ListApplicationKeysRequest) (*app.ListApplicationKeysResponse, error) {
|
||||
queries, err := convert.ListApplicationKeysRequestToDomain(s.systemDefaults, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := s.query.SearchAuthNKeys(ctx, queries, query.JoinFilterUnspecified, s.checkPermission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &app.ListApplicationKeysResponse{
|
||||
Keys: convert.ApplicationKeysToPb(res.AuthNKeys),
|
||||
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, res.SearchResponse),
|
||||
}, nil
|
||||
}
|
||||
|
@@ -177,7 +177,7 @@ func AddAPIClientKeyRequestToDomain(key *mgmt_pb.AddAppKeyRequest) *domain.Appli
|
||||
}
|
||||
|
||||
func ListAPIClientKeysRequestToQuery(ctx context.Context, req *mgmt_pb.ListAppKeysRequest) (*query.AuthNKeySearchQueries, error) {
|
||||
resourcOwner, err := query.NewAuthNKeyResourceOwnerQuery(authz.GetCtxData(ctx).OrgID)
|
||||
resourceOwner, err := query.NewAuthNKeyResourceOwnerQuery(authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -197,7 +197,7 @@ func ListAPIClientKeysRequestToQuery(ctx context.Context, req *mgmt_pb.ListAppKe
|
||||
Asc: asc,
|
||||
},
|
||||
Queries: []query.SearchQuery{
|
||||
resourcOwner,
|
||||
resourceOwner,
|
||||
projectID,
|
||||
appID,
|
||||
},
|
||||
|
@@ -38,6 +38,11 @@ func (c *Commands) AddApplicationKey(ctx context.Context, key *domain.Applicatio
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resourceOwner == "" {
|
||||
resourceOwner = application.ResourceOwner
|
||||
}
|
||||
|
||||
if !application.State.Exists() {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-sak25", "Errors.Project.App.NotFound")
|
||||
}
|
||||
@@ -59,6 +64,10 @@ func (c *Commands) addApplicationKey(ctx context.Context, key *domain.Applicatio
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := c.checkPermissionUpdateApplication(ctx, keyWriteModel.ResourceOwner, keyWriteModel.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !keyWriteModel.KeysAllowed {
|
||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Dff54", "Errors.Project.App.AuthMethodNoPrivateKeyJWT")
|
||||
}
|
||||
@@ -110,6 +119,10 @@ func (c *Commands) RemoveApplicationKey(ctx context.Context, projectID, applicat
|
||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-4m77G", "Errors.Project.App.Key.NotFound")
|
||||
}
|
||||
|
||||
if err := c.checkPermissionUpdateApplication(ctx, keyWriteModel.ResourceOwner, keyWriteModel.AggregateID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.Push(ctx, project.NewApplicationKeyRemovedEvent(ctx, ProjectAggregateFromWriteModel(&keyWriteModel.WriteModel), keyID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
permissionmock "github.com/zitadel/zitadel/internal/domain/mock"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/id"
|
||||
@@ -17,9 +18,10 @@ import (
|
||||
|
||||
func TestCommandSide_AddAPIApplicationKey(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
idGenerator id.Generator
|
||||
keySize int
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
idGenerator id.Generator
|
||||
keySize int
|
||||
permissionCheckMock domain.PermissionCheck
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
@@ -39,9 +41,8 @@ func TestCommandSide_AddAPIApplicationKey(t *testing.T) {
|
||||
{
|
||||
name: "no aggregateid, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
permissionCheckMock: permissionmock.MockPermissionCheckOK(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -57,9 +58,8 @@ func TestCommandSide_AddAPIApplicationKey(t *testing.T) {
|
||||
{
|
||||
name: "no appid, invalid argument error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
eventstore: expectEventstore(),
|
||||
permissionCheckMock: permissionmock.MockPermissionCheckOK(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -77,10 +77,8 @@ func TestCommandSide_AddAPIApplicationKey(t *testing.T) {
|
||||
{
|
||||
name: "app not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
),
|
||||
eventstore: expectEventstore(expectFilter()),
|
||||
permissionCheckMock: permissionmock.MockPermissionCheckOK(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -97,10 +95,9 @@ func TestCommandSide_AddAPIApplicationKey(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create key not allowed, precondition error",
|
||||
name: "create key not allowed, precondition error 1",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewApplicationAddedEvent(context.Background(),
|
||||
@@ -121,7 +118,8 @@ func TestCommandSide_AddAPIApplicationKey(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "key1"),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "key1"),
|
||||
permissionCheckMock: permissionmock.MockPermissionCheckOK(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -138,10 +136,9 @@ func TestCommandSide_AddAPIApplicationKey(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create key not allowed, precondition error",
|
||||
name: "permission check failed",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewApplicationAddedEvent(context.Background(),
|
||||
@@ -162,8 +159,9 @@ func TestCommandSide_AddAPIApplicationKey(t *testing.T) {
|
||||
),
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "key1"),
|
||||
keySize: 10,
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "key1"),
|
||||
keySize: 10,
|
||||
permissionCheckMock: permissionmock.MockPermissionCheckErr(zerrors.ThrowPermissionDenied(nil, "mock.err", "mock permission check failed")),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
@@ -175,6 +173,47 @@ func TestCommandSide_AddAPIApplicationKey(t *testing.T) {
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPermissionDenied,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create key not allowed, precondition error 2",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewApplicationAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"app1",
|
||||
"app",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
project.NewAPIConfigAddedEvent(context.Background(),
|
||||
&project.NewAggregate("project1", "org1").Aggregate,
|
||||
"app1",
|
||||
"client1@project",
|
||||
"secret",
|
||||
domain.APIAuthMethodTypeBasic),
|
||||
),
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "key1"),
|
||||
keySize: 10,
|
||||
permissionCheckMock: permissionmock.MockPermissionCheckOK(),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
key: &domain.ApplicationKey{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "project1",
|
||||
},
|
||||
ApplicationID: "app1",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: zerrors.IsPreconditionFailed,
|
||||
},
|
||||
@@ -183,9 +222,10 @@ func TestCommandSide_AddAPIApplicationKey(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
applicationKeySize: tt.fields.keySize,
|
||||
checkPermission: tt.fields.permissionCheckMock,
|
||||
}
|
||||
got, err := r.AddApplicationKey(tt.args.ctx, tt.args.key, tt.args.resourceOwner)
|
||||
if tt.res.err == nil {
|
||||
|
22
internal/domain/mock/permission.go
Normal file
22
internal/domain/mock/permission.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package permissionmock
|
||||
|
||||
import (
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
)
|
||||
|
||||
// MockPermissionCheckErr returns a permission check function that will fail
|
||||
// and return the input error
|
||||
func MockPermissionCheckErr(err error) domain.PermissionCheck {
|
||||
return func(_ context.Context, _, _, _ string) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// MockPermissionCheckOK returns a permission check function that will succeed
|
||||
func MockPermissionCheckOK() domain.PermissionCheck {
|
||||
return func(_ context.Context, _, _, _ string) (err error) {
|
||||
return nil
|
||||
}
|
||||
}
|
@@ -98,6 +98,7 @@ type AuthNKey struct {
|
||||
ChangeDate time.Time
|
||||
ResourceOwner string
|
||||
Sequence uint64
|
||||
ApplicationID string
|
||||
|
||||
Expiration time.Time
|
||||
Type domain.AuthNKeyType
|
||||
@@ -222,6 +223,19 @@ func (q *Queries) SearchAuthNKeysData(ctx context.Context, queries *AuthNKeySear
|
||||
return authNKeys, err
|
||||
}
|
||||
|
||||
func (q *Queries) GetAuthNKeyByIDWithPermission(ctx context.Context, shouldTriggerBulk bool, id string, permissionCheck domain.PermissionCheck, queries ...SearchQuery) (*AuthNKey, error) {
|
||||
key, err := q.GetAuthNKeyByID(ctx, shouldTriggerBulk, id, queries...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := appCheckPermission(ctx, key.ResourceOwner, key.AggregateID, permissionCheck); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (q *Queries) GetAuthNKeyByID(ctx context.Context, shouldTriggerBulk bool, id string, queries ...SearchQuery) (key *AuthNKey, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
@@ -330,6 +344,7 @@ func prepareAuthNKeysQuery() (sq.SelectBuilder, func(rows *sql.Rows) (*AuthNKeys
|
||||
AuthNKeyColumnSequence.identifier(),
|
||||
AuthNKeyColumnExpiration.identifier(),
|
||||
AuthNKeyColumnType.identifier(),
|
||||
AuthNKeyColumnObjectID.identifier(),
|
||||
countColumn.identifier(),
|
||||
).From(authNKeyTable.identifier()).
|
||||
PlaceholderFormat(sq.Dollar)
|
||||
@@ -348,6 +363,7 @@ func prepareAuthNKeysQuery() (sq.SelectBuilder, func(rows *sql.Rows) (*AuthNKeys
|
||||
&authNKey.Sequence,
|
||||
&authNKey.Expiration,
|
||||
&authNKey.Type,
|
||||
&authNKey.ApplicationID,
|
||||
&count,
|
||||
)
|
||||
if err != nil {
|
||||
|
@@ -26,6 +26,7 @@ var (
|
||||
` projections.authn_keys2.sequence,` +
|
||||
` projections.authn_keys2.expiration,` +
|
||||
` projections.authn_keys2.type,` +
|
||||
` projections.authn_keys2.object_id,` +
|
||||
` COUNT(*) OVER ()` +
|
||||
` FROM projections.authn_keys2`
|
||||
prepareAuthNKeysCols = []string{
|
||||
@@ -37,6 +38,7 @@ var (
|
||||
"sequence",
|
||||
"expiration",
|
||||
"type",
|
||||
"object_id",
|
||||
"count",
|
||||
}
|
||||
|
||||
@@ -129,6 +131,7 @@ func Test_AuthNKeyPrepares(t *testing.T) {
|
||||
uint64(20211109),
|
||||
testNow,
|
||||
1,
|
||||
"app1",
|
||||
},
|
||||
},
|
||||
),
|
||||
@@ -147,6 +150,7 @@ func Test_AuthNKeyPrepares(t *testing.T) {
|
||||
Sequence: 20211109,
|
||||
Expiration: testNow,
|
||||
Type: domain.AuthNKeyTypeJSON,
|
||||
ApplicationID: "app1",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -168,6 +172,7 @@ func Test_AuthNKeyPrepares(t *testing.T) {
|
||||
uint64(20211109),
|
||||
testNow,
|
||||
1,
|
||||
"app1",
|
||||
},
|
||||
{
|
||||
"id-2",
|
||||
@@ -178,6 +183,7 @@ func Test_AuthNKeyPrepares(t *testing.T) {
|
||||
uint64(20211109),
|
||||
testNow,
|
||||
1,
|
||||
"app1",
|
||||
},
|
||||
},
|
||||
),
|
||||
@@ -196,6 +202,7 @@ func Test_AuthNKeyPrepares(t *testing.T) {
|
||||
Sequence: 20211109,
|
||||
Expiration: testNow,
|
||||
Type: domain.AuthNKeyTypeJSON,
|
||||
ApplicationID: "app1",
|
||||
},
|
||||
{
|
||||
ID: "id-2",
|
||||
@@ -206,6 +213,7 @@ func Test_AuthNKeyPrepares(t *testing.T) {
|
||||
Sequence: 20211109,
|
||||
Expiration: testNow,
|
||||
Type: domain.AuthNKeyTypeJSON,
|
||||
ApplicationID: "app1",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@@ -92,3 +92,30 @@ message ApplicationNameQuery {
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
enum ApplicationKeysSorting {
|
||||
APPLICATION_KEYS_SORT_BY_ID = 0;
|
||||
APPLICATION_KEYS_SORT_BY_PROJECT_ID = 1;
|
||||
APPLICATION_KEYS_SORT_BY_APPLICATION_ID = 2;
|
||||
APPLICATION_KEYS_SORT_BY_CREATION_DATE = 3;
|
||||
APPLICATION_KEYS_SORT_BY_ORGANIZATION_ID = 4;
|
||||
APPLICATION_KEYS_SORT_BY_EXPIRATION = 5;
|
||||
APPLICATION_KEYS_SORT_BY_TYPE = 6;
|
||||
}
|
||||
|
||||
message ApplicationKey {
|
||||
string id = 1;
|
||||
string application_id = 2;
|
||||
string project_id = 3;
|
||||
google.protobuf.Timestamp creation_date = 4 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"2024-12-18T07:50:47.492Z\"";
|
||||
}
|
||||
];
|
||||
string organization_id = 5;
|
||||
google.protobuf.Timestamp expiration_date = 6 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"2024-12-18T07:50:47.492Z\"";
|
||||
}
|
||||
];
|
||||
}
|
@@ -114,8 +114,6 @@ service AppService {
|
||||
//
|
||||
// Create an application. The application can be OIDC, API or SAML type, based on the input.
|
||||
//
|
||||
// The user needs to have project.app.write permission
|
||||
//
|
||||
// Required permissions:
|
||||
// - project.app.write
|
||||
rpc CreateApplication(CreateApplicationRequest) returns (CreateApplicationResponse) {
|
||||
@@ -145,8 +143,6 @@ service AppService {
|
||||
// Changes the configuration of an OIDC, API or SAML type application, as well as
|
||||
// the application name, based on the input provided.
|
||||
//
|
||||
// The user needs to have project.app.write permission
|
||||
//
|
||||
// Required permissions:
|
||||
// - project.app.write
|
||||
rpc UpdateApplication(UpdateApplicationRequest) returns (UpdateApplicationResponse) {
|
||||
@@ -175,8 +171,6 @@ service AppService {
|
||||
//
|
||||
// Retrieves the application matching the provided ID.
|
||||
//
|
||||
// The user needs to have project.app.read permission
|
||||
//
|
||||
// Required permissions:
|
||||
// - project.app.read
|
||||
rpc GetApplication(GetApplicationRequest) returns (GetApplicationResponse) {
|
||||
@@ -203,9 +197,7 @@ service AppService {
|
||||
// Delete Application
|
||||
//
|
||||
// Deletes the application belonging to the input project and matching the provided
|
||||
// application ID
|
||||
//
|
||||
// The user needs to have project.app.delete permission
|
||||
// application ID.
|
||||
//
|
||||
// Required permissions:
|
||||
// - project.app.delete
|
||||
@@ -233,9 +225,7 @@ service AppService {
|
||||
// Deactivate Application
|
||||
//
|
||||
// Deactivates the application belonging to the input project and matching the provided
|
||||
// application ID
|
||||
//
|
||||
// The user needs to have project.app.write permission
|
||||
// application ID.
|
||||
//
|
||||
// Required permissions:
|
||||
// - project.app.write
|
||||
@@ -264,9 +254,7 @@ service AppService {
|
||||
// Reactivate Application
|
||||
//
|
||||
// Reactivates the application belonging to the input project and matching the provided
|
||||
// application ID
|
||||
//
|
||||
// The user needs to have project.app.write permission
|
||||
// application ID.
|
||||
//
|
||||
// Required permissions:
|
||||
// - project.app.write
|
||||
@@ -297,8 +285,6 @@ service AppService {
|
||||
//
|
||||
// Regenerates the client secret of an API or OIDC application that belongs to the input project.
|
||||
//
|
||||
// The user needs to have project.app.write permission
|
||||
//
|
||||
// Required permissions:
|
||||
// - project.app.write
|
||||
rpc RegenerateClientSecret(RegenerateClientSecretRequest) returns (RegenerateClientSecretResponse) {
|
||||
@@ -331,8 +317,6 @@ service AppService {
|
||||
// The result can be sorted by app id, name, creation date, change date or state. It can also
|
||||
// be filtered by app state, app type and app name.
|
||||
//
|
||||
// The user needs to have project.app.read permission
|
||||
//
|
||||
// Required permissions:
|
||||
// - project.app.read
|
||||
rpc ListApplications(ListApplicationsRequest) returns (ListApplicationsResponse) {
|
||||
@@ -356,6 +340,129 @@ service AppService {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Create Application Key
|
||||
//
|
||||
// Create a new application key, which is used to authorize an API application.
|
||||
//
|
||||
// Key details are returned in the response. They must be stored safely, as it will not
|
||||
// be possible to retrieve them again.
|
||||
//
|
||||
// Required permissions:
|
||||
// - `project.app.write`
|
||||
rpc CreateApplicationKey(CreateApplicationKeyRequest) returns (CreateApplicationKeyResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v2beta/application_keys"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
auth_option: {
|
||||
permission: "authenticated"
|
||||
}
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
responses: {
|
||||
key: "200";
|
||||
value: {
|
||||
description: "The created application key";
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Delete Application Key
|
||||
//
|
||||
// Deletes an application key matching the provided ID.
|
||||
//
|
||||
// Organization ID is not mandatory, but helps with filtering/performance.
|
||||
//
|
||||
// The deletion time is returned in response message.
|
||||
//
|
||||
// Required permissions:
|
||||
// - `project.app.write`
|
||||
rpc DeleteApplicationKey(DeleteApplicationKeyRequest) returns (DeleteApplicationKeyResponse) {
|
||||
option (google.api.http) = {
|
||||
delete: "/v2beta/application_keys/{id}"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
auth_option: {
|
||||
permission: "authenticated"
|
||||
}
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
responses: {
|
||||
key: "200";
|
||||
value: {
|
||||
description: "The time of deletion.";
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Get Application Key
|
||||
//
|
||||
// Retrieves the application key matching the provided ID.
|
||||
//
|
||||
// Specifying a project, organization and app ID is optional but help with filtering/performance.
|
||||
//
|
||||
// Required permissions:
|
||||
// - project.app.read
|
||||
rpc GetApplicationKey(GetApplicationKeyRequest) returns (GetApplicationKeyResponse) {
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
responses: {
|
||||
key: "200";
|
||||
value: {
|
||||
description: "The fetched app key.";
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
option (google.api.http) = {
|
||||
get: "/v2beta/application_keys/{id}"
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
auth_option: {
|
||||
permission: "authenticated"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// List Application Keys
|
||||
//
|
||||
// Returns a list of application keys matching the input parameters.
|
||||
//
|
||||
// The result can be sorted by id, aggregate, creation date, expiration date, resource owner or type.
|
||||
// It can also be filtered by app, project or organization ID.
|
||||
//
|
||||
// Required permissions:
|
||||
// - project.app.read
|
||||
rpc ListApplicationKeys(ListApplicationKeysRequest) returns (ListApplicationKeysResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/v2beta/application_keys/search"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
responses: {
|
||||
key: "200";
|
||||
value: {
|
||||
description: "The matching applications";
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||
auth_option: {
|
||||
permission: "authenticated"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
message CreateApplicationRequest {
|
||||
@@ -785,4 +892,103 @@ message ListApplicationsResponse {
|
||||
|
||||
// Contains the total number of apps matching the query and the applied limit.
|
||||
zitadel.filter.v2.PaginationResponse pagination = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message CreateApplicationKeyRequest {
|
||||
string app_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
|
||||
string project_id = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
|
||||
// The date the key will expire
|
||||
google.protobuf.Timestamp expiration_date = 3 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"2519-04-01T08:45:00.000000Z\"";
|
||||
description: "The date the key will expire";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message CreateApplicationKeyResponse {
|
||||
string id = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"28746028909593987\"";
|
||||
}
|
||||
];
|
||||
|
||||
// The timestamp of the app creation.
|
||||
google.protobuf.Timestamp creation_date = 2 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"2024-12-18T07:50:47.492Z\"";
|
||||
}
|
||||
];
|
||||
|
||||
bytes key_details = 3 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"eyJ0eXBlIjoiYXBwbGljYXRpb24iLCJrZXlJZCI6IjIwMjcxMDE4NjYyMjcxNDExMyIsImtleSI6Ii0tLS0tQkVHSU4gUlNBIFBSSVZBVEUgS0VZLS0tLS1cbk1JSUVvd0lCQUFLQ0FRRUFuMUxyNStTV0pGRllURU1kaXQ2U0dNY0E2Yks5dG0xMmhlcm55V0wrZm9PWnA3eEVcbk9wcmsvWE81QVplSU5NY0x0ZVhxckJlK1NPdVVNMFpLU2xCMHFTNzNjVStDVTVMTGoycVB0UzhNOFI0N3BGdFhcbjJXRTFJNjNhZHB1N01TejA2SXduQ2lyNnJYOTVPQ2ZneHA3VU1Dd0pSTUZmYXJqdjVBRXY3NXpsSS9lYUV6bUJcbkxKWU1xanZFRmZoN2x3M2lPT3VsWW9kNjNpN3RDNWl5czNlYjNLZW4yWU0rN1FSbXB2dE5qcTJMVmlIMnkrUGJcbk9ESlI3MU9ib05TYVJDNTZDUFpWVytoWDByYXI3VzMwUjI2eGtIQ09oSytQbUpSeGtOY0g1VTdja0xXMEw0WEVcbnNNZkVUSmszeDR3Q0psbisxbElXUzkrNmw0R1E2TWRzWURyOU5RSURBUUFCQW9JQkFCSkx6WGQxMHFBZEQwekNcbnNGUFFOMnJNLzVmV3hONThONDR0YWF6QXg0VHp5K050UlZDTmxScGQvYkxuR2VjbHJIeVpDSmYycWcxcHNEMHJcbkowRGRlR2d0VXBFYWxsYk9scjNEZVBsUGkrYnNsK0RKOUk2c0VSUWwxTjZtQjVzZ0ZJZllBR3UwZjlFSXdIem9cblozR25yNnBRaEVmM0JPUVdsTVhVTlJNSksyOHp3M2E1L01nRmtKVUZUSTUzeXFwbGRtZ2hLajRZR1hLRk1LUGhcbkV3RkxrRncwK2s3K0xuSjFQNGp1ZVd1RXo3WlAyaFpvUWxCcXdSajVyTG9QZ05RbUU4UytFVDRuczlUYzByOFFcbnFyaHlacDZBczJrTDhGTytCZnF3SVpDZnpnWHN2cC9PLzRaSHIzVTB2Ymp3UW1sSzdVSm42U0J6T2hpWFpNU0lcbk5Wc0V5VUVDZ1lFQTFEaktkRGo3NTM1MWQzdlRNQlRFd2JSQ3hoUVZOdENFMnMwVUw4ckJQZ1I0K1dlblNUWmFcbnprWUprcEV0bE54VGxzYnN1Y0RTUXZqeWRYYk5nSHFBeDYzMm1vdTVkak9lR0VTUDFWVGtUdElsZFZQZWszQWxcbjVYbkpQa1dqWGVyVVJZNm5KeUQ5UWhlREx3MVp4NEFYVzNHWURiTFkrT05XV0VKUlJaQUloNjBDZ1lFQXdEQ2xcbnc1MHc4dkcvbEJ4RzNSYW9FaHdLOWNna1VXOHk2T25DekNwcEtjOEZUUmY1VE5iWjl5TzNXUmdYajhkeHRCakFcbkl5VGlzYk9NQk1VaFZKUUtGZHRQaDhoVDBwRkRjeE9ndzY0aHBtYzhyY2RTbXVKNzlYSVRTaHUySjA0N0UvNFZcbnJOTThpWVk5ZGR3VGdGUUlsdFNZL0l0RnFxWERmdjhqK1dVY25La0NnWUVBaENOUU80bDNuNjRucWR2WnBTaHBcblVrclJBTkJrWFJyOGZkZ1BaNnFSSS9KWStNSEhjVmg4dGM3NkN0NkdTUmZlbkJVRU5LeVF2czZPK1FDZCtBOU9cbnZBWGZkRjduZldlcVdtWG1RT2g0dDNNMWk1WkxFZlpVUWt2UU9BdllLcFFhMDZ4OCsyb1pCdHZvL0pVTmY2Q0xcbjZvNFNKUVZrLzZOZGtkckpDODBnNG9rQ2dZQkZsNWYrbkVYa1F0dWZVeG5wNXRGWE5XWldsM0ZuTjMvVXpRaW5cbmkxZm5OcnB4cnhPcjJrUzA4KzdwU1FzSEdpNDNDNXRQWG9UajJlTUN1eXNWaUVHYXBuNUc2YWhJb0NjdlhWVWlcblprUnpFQUR0NERZdU5ZS3pYdXBUTkhPaUNmYmtoMlhyM2RXVzZ0QUloSGRmU1k2T3AwNzZhNmYvWWVUSGNMWGpcbkVkVHBlUUtCZ0FPdnBqcDQ4TzRUWEZkU0JLSnYya005OHVhUjlSQURtdGxTWHd2cTlyQkhTV084NFk4bzE0L1Bcbkl1UmxUOHhROGRYKzhMR21UUCtjcUtiOFFRQ1grQk1YUWxMSEVtWnpnb0xFa0pGMUVIMm4vZEZ5bngxS3prdFNcbm9UZUdsRzZhbXhVOVh4eW9RVFlEVGJCbERwc2FZUlFBZ2FUQzM3UVZRUjhmK1ZoRzFHSFFcbi0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tXG4iLCJhcHBJZCI6IjIwMjcwNjM5ODgxMzg4MDU3NyIsImNsaWVudElkIjoiMjAyNzA2Mzk4ODEzOTQ2MTEzQG15dGVzdHByb2plY3QifQ==\"";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message DeleteApplicationKeyRequest {
|
||||
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
string project_id = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
string application_id = 3 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
string organization_id = 4 [(validate.rules).string = {max_len: 200}];
|
||||
}
|
||||
|
||||
message DeleteApplicationKeyResponse {
|
||||
google.protobuf.Timestamp deletion_date = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message GetApplicationKeyRequest {
|
||||
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
string project_id = 2 [(validate.rules).string = {max_len: 200}];
|
||||
string application_id = 3 [(validate.rules).string = {max_len: 200}];
|
||||
string organization_id = 4 [(validate.rules).string = {max_len: 200}];
|
||||
}
|
||||
|
||||
message GetApplicationKeyResponse {
|
||||
string id = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"69629023906488334\"";
|
||||
}
|
||||
];
|
||||
|
||||
google.protobuf.Timestamp creation_date = 2 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||
}
|
||||
];
|
||||
|
||||
// the date a key will expire
|
||||
google.protobuf.Timestamp expiration_date = 3 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "the date a key will expire";
|
||||
example: "\"3019-04-01T08:45:00.000000Z\"";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message ListApplicationKeysRequest {
|
||||
// Pagination and sorting.
|
||||
zitadel.filter.v2.PaginationRequest pagination = 1;
|
||||
|
||||
ApplicationKeysSorting sorting_column = 2;
|
||||
|
||||
oneof resource_id {
|
||||
string application_id = 3 [(validate.rules).string = {min_len: 1; max_len: 200}];
|
||||
string project_id = 4 [(validate.rules).string = {min_len: 1; max_len: 200}];
|
||||
string organization_id = 5 [(validate.rules).string = {min_len: 1; max_len: 200}];
|
||||
}
|
||||
}
|
||||
|
||||
message ListApplicationKeysResponse {
|
||||
repeated ApplicationKey keys = 1;
|
||||
|
||||
// Contains the total number of app keys matching the query and the applied limit.
|
||||
zitadel.filter.v2.PaginationResponse pagination = 2;
|
||||
}
|
||||
|
@@ -3709,6 +3709,7 @@ service ManagementService {
|
||||
};
|
||||
}
|
||||
|
||||
// Deprecated: Use [GetApplicationKey](/apis/resources/application_service_v2/application-service-get-application-key.api.mdx) instead to get an application key
|
||||
rpc GetAppKey(GetAppKeyRequest) returns (GetAppKeyResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/projects/{project_id}/apps/{app_id}/keys/{key_id}"
|
||||
@@ -3731,9 +3732,11 @@ service ManagementService {
|
||||
required: false;
|
||||
};
|
||||
};
|
||||
deprecated: true;
|
||||
};
|
||||
}
|
||||
|
||||
// Deprecated: Use [ListApplicationKeys](/apis/resources/application_service_v2/application-service-list-application-keys.api.mdx) instead to list application keys
|
||||
rpc ListAppKeys(ListAppKeysRequest) returns (ListAppKeysResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/projects/{project_id}/apps/{app_id}/keys/_search"
|
||||
@@ -3760,6 +3763,8 @@ service ManagementService {
|
||||
};
|
||||
}
|
||||
|
||||
// Deprecated: Use [CreateApplicationKey](/apis/resources/application_service_v2/application-service-create-application-key.api.mdx) instead to
|
||||
// create an application key
|
||||
rpc AddAppKey(AddAppKeyRequest) returns (AddAppKeyResponse){
|
||||
option (google.api.http) = {
|
||||
post: "/projects/{project_id}/apps/{app_id}/keys"
|
||||
@@ -3783,9 +3788,12 @@ service ManagementService {
|
||||
required: false;
|
||||
};
|
||||
};
|
||||
deprecated: true;
|
||||
};
|
||||
}
|
||||
|
||||
// Deprecated: Use [DeleteApplicationKey](/apis/resources/application_service_v2/application-service-delete-application-key.api.mdx) instead to
|
||||
// delete an application key
|
||||
rpc RemoveAppKey(RemoveAppKeyRequest) returns (RemoveAppKeyResponse) {
|
||||
option (google.api.http) = {
|
||||
delete: "/projects/{project_id}/apps/{app_id}/keys/{key_id}"
|
||||
@@ -3808,6 +3816,7 @@ service ManagementService {
|
||||
required: false;
|
||||
};
|
||||
};
|
||||
deprecated: true;
|
||||
};
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user