Files
zitadel/internal/api/grpc/application/v2/convert/convert.go
T
Livio SpringandGitHub 7d6a2950b4 feat(api): allow filtering apps by client id and entity id (#11385)
# Which Problems Are Solved

There's currently no way to retrieve a project based on the client_id of
an application. A developer would need to iterate through all projects
and their applications to make the correlation.

# How the Problems Are Solved

- Added a possibility to filter for `client_id` and `entity_id` on the
ListApplications endpoint.
- Added the `project_id` on the `Application` response (used on the
ListApplications endpoint).

# Additional Changes

None

# Additional Context

- closes #11340 
- requires backport to v4.x
2026-01-22 07:41:35 +00:00

279 lines
9.8 KiB
Go

package convert
import (
"net/url"
"strings"
"github.com/muhlemmer/gu"
"google.golang.org/protobuf/types/known/timestamppb"
"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"
"github.com/zitadel/zitadel/pkg/grpc/application/v2"
)
func AppToPb(queryApp *query.App) *application.Application {
if queryApp == nil {
return &application.Application{}
}
return &application.Application{
ApplicationId: queryApp.ID,
CreationDate: timestamppb.New(queryApp.CreationDate),
ChangeDate: timestamppb.New(queryApp.ChangeDate),
State: appStateToPb(queryApp.State),
Name: queryApp.Name,
Configuration: appConfigToPb(queryApp),
ProjectId: queryApp.ProjectID,
}
}
func AppsToPb(queryApps []*query.App) []*application.Application {
pbApps := make([]*application.Application, len(queryApps))
for i, queryApp := range queryApps {
pbApps[i] = AppToPb(queryApp)
}
return pbApps
}
func ListApplicationsRequestToModel(sysDefaults systemdefaults.SystemDefaults, req *application.ListApplicationsRequest) (*query.AppSearchQueries, error) {
offset, limit, asc, err := filter.PaginationPbToQuery(sysDefaults, req.GetPagination())
if err != nil {
return nil, err
}
queries, err := appQueriesToModel(req.GetFilters())
if err != nil {
return nil, err
}
return &query.AppSearchQueries{
SearchRequest: query.SearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
SortingColumn: appSortingToColumn(req.GetSortingColumn()),
},
Queries: queries,
}, nil
}
func appSortingToColumn(sortingCriteria application.ApplicationSorting) query.Column {
switch sortingCriteria {
case application.ApplicationSorting_APPLICATION_SORT_BY_CHANGE_DATE:
return query.AppColumnChangeDate
case application.ApplicationSorting_APPLICATION_SORT_BY_CREATION_DATE:
return query.AppColumnCreationDate
case application.ApplicationSorting_APPLICATION_SORT_BY_NAME:
return query.AppColumnName
case application.ApplicationSorting_APPLICATION_SORT_BY_STATE:
return query.AppColumnState
case application.ApplicationSorting_APPLICATION_SORT_BY_ID:
fallthrough
default:
return query.AppColumnID
}
}
func appStateToPb(state domain.AppState) application.ApplicationState {
switch state {
case domain.AppStateActive:
return application.ApplicationState_APPLICATION_STATE_ACTIVE
case domain.AppStateInactive:
return application.ApplicationState_APPLICATION_STATE_INACTIVE
case domain.AppStateRemoved:
return application.ApplicationState_APPLICATION_STATE_REMOVED
case domain.AppStateUnspecified:
fallthrough
default:
return application.ApplicationState_APPLICATION_STATE_UNSPECIFIED
}
}
func appConfigToPb(app *query.App) application.IsApplicationConfiguration {
if app.OIDCConfig != nil {
return appOIDCConfigToPb(app.OIDCConfig)
}
if app.SAMLConfig != nil {
return appSAMLConfigToPb(app.SAMLConfig)
}
return appAPIConfigToPb(app.APIConfig)
}
func loginVersionToDomain(version *application.LoginVersion) (*domain.LoginVersion, *string, error) {
switch v := version.GetVersion().(type) {
case nil:
return gu.Ptr(domain.LoginVersionUnspecified), gu.Ptr(""), nil
case *application.LoginVersion_LoginV1:
return gu.Ptr(domain.LoginVersion1), gu.Ptr(""), nil
case *application.LoginVersion_LoginV2:
_, err := url.Parse(v.LoginV2.GetBaseUri())
return gu.Ptr(domain.LoginVersion2), gu.Ptr(v.LoginV2.GetBaseUri()), err
default:
return gu.Ptr(domain.LoginVersionUnspecified), gu.Ptr(""), nil
}
}
func loginVersionToPb(version domain.LoginVersion, baseURI *string) *application.LoginVersion {
switch version {
case domain.LoginVersionUnspecified:
return nil
case domain.LoginVersion1:
return &application.LoginVersion{Version: &application.LoginVersion_LoginV1{LoginV1: &application.LoginV1{}}}
case domain.LoginVersion2:
return &application.LoginVersion{Version: &application.LoginVersion_LoginV2{LoginV2: &application.LoginV2{BaseUri: baseURI}}}
default:
return nil
}
}
func appQueriesToModel(filters []*application.ApplicationSearchFilter) (queries []query.SearchQuery, err error) {
queries = make([]query.SearchQuery, len(filters))
for i, f := range filters {
queries[i], err = applicationFilterToQuery(f)
if err != nil {
return nil, err
}
}
return queries, nil
}
func applicationFilterToQuery(applicationFilter *application.ApplicationSearchFilter) (query.SearchQuery, error) {
switch q := applicationFilter.GetFilter().(type) {
case *application.ApplicationSearchFilter_ProjectIdFilter:
return query.NewAppProjectIDSearchQuery(q.ProjectIdFilter.GetProjectId())
case *application.ApplicationSearchFilter_NameFilter:
return query.NewAppNameSearchQuery(filter.TextMethodPbToQuery(q.NameFilter.GetMethod()), q.NameFilter.GetName())
case *application.ApplicationSearchFilter_StateFilter:
return query.NewAppStateSearchQuery(domain.AppState(q.StateFilter))
case *application.ApplicationSearchFilter_TypeFilter:
return applicationTypeFilterToQuery(q.TypeFilter)
case *application.ApplicationSearchFilter_ClientIdFilter:
return query.NewAppClientIDSearchQuery(strings.TrimSpace(q.ClientIdFilter.GetClientId()))
case *application.ApplicationSearchFilter_EntityIdFilter:
return query.NewAppSAMLEntityIDSearchQuery(strings.TrimSpace(q.EntityIdFilter.GetEntityId()))
default:
return nil, zerrors.ThrowInvalidArgument(nil, "CONV-z2mAGy", "List.Query.Invalid")
}
}
func applicationTypeFilterToQuery(t application.ApplicationType) (*query.NotNullQuery, error) {
switch t {
case application.ApplicationType_APPLICATION_TYPE_OIDC:
return query.NewNotNullQuery(query.AppOIDCConfigColumnAppID)
case application.ApplicationType_APPLICATION_TYPE_API:
return query.NewNotNullQuery(query.AppAPIConfigColumnAppID)
case application.ApplicationType_APPLICATION_TYPE_SAML:
return query.NewNotNullQuery(query.AppSAMLConfigColumnAppID)
case application.ApplicationType_APPLICATION_TYPE_UNSPECIFIED:
return nil, zerrors.ThrowInvalidArgument(nil, "CONV-Jke83s", "List.Query.Invalid")
default:
return nil, zerrors.ThrowInvalidArgument(nil, "CONV-Skj3q", "List.Query.Invalid")
}
}
func CreateAPIClientKeyRequestToDomain(key *application.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.GetApplicationId()),
}
}
func ListApplicationKeysRequestToDomain(sysDefaults systemdefaults.SystemDefaults, req *application.ListApplicationKeysRequest) (*query.AuthNKeySearchQueries, error) {
var queries []query.SearchQuery
queries, err := ApplicationKeySearchQueriesToQuery(req.GetFilters())
if err != nil {
return nil, err
}
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 ApplicationKeySearchQueriesToQuery(queries []*application.ApplicationKeySearchFilter) (_ []query.SearchQuery, err error) {
searchQueries := make([]query.SearchQuery, len(queries))
for i, query := range queries {
searchQueries[i], err = ApplicationKeySearchFilterToQuery(query)
if err != nil {
return nil, err
}
}
return searchQueries, nil
}
func ApplicationKeySearchFilterToQuery(searchQuery *application.ApplicationKeySearchFilter) (query.SearchQuery, error) {
switch f := searchQuery.GetFilter().(type) {
case *application.ApplicationKeySearchFilter_ApplicationIdFilter:
return query.NewAuthNKeyObjectIDQuery(strings.TrimSpace(f.ApplicationIdFilter.GetApplicationId()))
case *application.ApplicationKeySearchFilter_OrganizationIdFilter:
return query.NewAuthNKeyResourceOwnerQuery(strings.TrimSpace(f.OrganizationIdFilter.GetOrganizationId()))
case *application.ApplicationKeySearchFilter_ProjectIdFilter:
return query.NewAuthNKeyAggregateIDQuery(strings.TrimSpace(f.ProjectIdFilter.GetProjectId()))
default:
return nil, zerrors.ThrowInvalidArgument(nil, "CONV-t3ENme", "List.Query.Invalid")
}
}
func appKeysSortingToColumn(sortingCriteria application.ApplicationKeysSorting) query.Column {
switch sortingCriteria {
case application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_PROJECT_ID:
return query.AuthNKeyColumnAggregateID
case application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_CREATION_DATE:
return query.AuthNKeyColumnCreationDate
case application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_EXPIRATION:
return query.AuthNKeyColumnExpiration
case application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_ORGANIZATION_ID:
return query.AuthNKeyColumnResourceOwner
case application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_TYPE:
return query.AuthNKeyColumnType
case application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_APPLICATION_ID:
return query.AuthNKeyColumnObjectID
case application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_ID:
fallthrough
default:
return query.AuthNKeyColumnID
}
}
func ApplicationKeysToPb(keys []*query.AuthNKey) []*application.ApplicationKey {
pbAppKeys := make([]*application.ApplicationKey, len(keys))
for i, k := range keys {
pbKey := &application.ApplicationKey{
KeyId: 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
}