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:
Marco A.
2025-07-02 09:34:19 +02:00
committed by GitHub
parent 64a03fba28
commit fce9e770ac
19 changed files with 1350 additions and 69 deletions

View File

@@ -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)
})
}
}