mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:27:42 +00:00
feat: App API v2 (#10077)
# Which Problems Are Solved This PR *partially* addresses #9450 . Specifically, it implements the resource based API for the apps. APIs for app keys ARE not part of this PR. # How the Problems Are Solved - `CreateApplication`, `PatchApplication` (update) and `RegenerateClientSecret` endpoints are now unique for all app types: API, SAML and OIDC apps. - All new endpoints have integration tests - All new endpoints are using permission checks V2 # Additional Changes - The `ListApplications` endpoint allows to do sorting (see protobuf for details) and filtering by app type (see protobuf). - SAML and OIDC update endpoint can now receive requests for partial updates # Additional Context Partially addresses #9450
This commit is contained in:
@@ -5,9 +5,11 @@ import (
|
||||
"database/sql"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
@@ -307,6 +309,19 @@ func (q *Queries) AppByProjectAndAppID(ctx context.Context, shouldTriggerBulk bo
|
||||
return app, err
|
||||
}
|
||||
|
||||
func (q *Queries) AppByIDWithPermission(ctx context.Context, appID string, activeOnly bool, permissionCheck domain.PermissionCheck) (*App, error) {
|
||||
app, err := q.AppByID(ctx, appID, activeOnly)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := appCheckPermission(ctx, app.ResourceOwner, app.ProjectID, permissionCheck); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return app, nil
|
||||
}
|
||||
|
||||
func (q *Queries) AppByID(ctx context.Context, appID string, activeOnly bool) (app *App, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
@@ -476,11 +491,54 @@ func (q *Queries) AppByOIDCClientID(ctx context.Context, clientID string) (app *
|
||||
return app, err
|
||||
}
|
||||
|
||||
func (q *Queries) SearchApps(ctx context.Context, queries *AppSearchQueries, withOwnerRemoved bool) (apps *Apps, err error) {
|
||||
func (q *Queries) AppByClientID(ctx context.Context, clientID string) (app *App, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
stmt, scan := prepareAppQuery(true)
|
||||
eq := sq.Eq{
|
||||
AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
|
||||
AppColumnState.identifier(): domain.AppStateActive,
|
||||
ProjectColumnState.identifier(): domain.ProjectStateActive,
|
||||
OrgColumnState.identifier(): domain.OrgStateActive,
|
||||
}
|
||||
query, args, err := stmt.Where(sq.And{
|
||||
eq,
|
||||
sq.Or{
|
||||
sq.Eq{AppOIDCConfigColumnClientID.identifier(): clientID},
|
||||
sq.Eq{AppAPIConfigColumnClientID.identifier(): clientID},
|
||||
},
|
||||
}).ToSql()
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "QUERY-Dfge2", "Errors.Query.SQLStatement")
|
||||
}
|
||||
|
||||
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
||||
app, err = scan(row)
|
||||
return err
|
||||
}, query, args...)
|
||||
return app, err
|
||||
}
|
||||
|
||||
func (q *Queries) SearchApps(ctx context.Context, queries *AppSearchQueries, permissionCheck domain.PermissionCheck) (*Apps, error) {
|
||||
apps, err := q.searchApps(ctx, queries, PermissionV2(ctx, permissionCheck))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if permissionCheck != nil && !authz.GetFeatures(ctx).PermissionCheckV2 {
|
||||
apps.Apps = appsCheckPermission(ctx, apps.Apps, permissionCheck)
|
||||
}
|
||||
return apps, nil
|
||||
}
|
||||
|
||||
func (q *Queries) searchApps(ctx context.Context, queries *AppSearchQueries, isPermissionV2Enabled bool) (apps *Apps, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
query, scan := prepareAppsQuery()
|
||||
query = appPermissionCheckV2(ctx, query, isPermissionV2Enabled, queries)
|
||||
|
||||
eq := sq.Eq{AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()}
|
||||
stmt, args, err := queries.toQuery(query).Where(eq).ToSql()
|
||||
if err != nil {
|
||||
@@ -498,6 +556,21 @@ func (q *Queries) SearchApps(ctx context.Context, queries *AppSearchQueries, wit
|
||||
return apps, err
|
||||
}
|
||||
|
||||
func appPermissionCheckV2(ctx context.Context, query sq.SelectBuilder, enabled bool, queries *AppSearchQueries) sq.SelectBuilder {
|
||||
if !enabled {
|
||||
return query
|
||||
}
|
||||
|
||||
join, args := PermissionClause(
|
||||
ctx,
|
||||
AppColumnResourceOwner,
|
||||
domain.PermissionProjectAppRead,
|
||||
SingleOrgPermissionOption(queries.Queries),
|
||||
WithProjectsPermissionOption(AppColumnProjectID),
|
||||
)
|
||||
return query.JoinClause(join, args...)
|
||||
}
|
||||
|
||||
func (q *Queries) SearchClientIDs(ctx context.Context, queries *AppSearchQueries, shouldTriggerBulk bool) (ids []string, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
@@ -574,10 +647,25 @@ func (q *Queries) SAMLAppLoginVersion(ctx context.Context, appID string) (loginV
|
||||
return loginVersion, nil
|
||||
}
|
||||
|
||||
func appCheckPermission(ctx context.Context, resourceOwner string, projectID string, permissionCheck domain.PermissionCheck) error {
|
||||
return permissionCheck(ctx, domain.PermissionProjectAppRead, resourceOwner, projectID)
|
||||
}
|
||||
|
||||
// appsCheckPermission returns only the apps that the user in context has permission to read
|
||||
func appsCheckPermission(ctx context.Context, apps []*App, permissionCheck domain.PermissionCheck) []*App {
|
||||
return slices.DeleteFunc(apps, func(app *App) bool {
|
||||
return permissionCheck(ctx, domain.PermissionProjectAppRead, app.ResourceOwner, app.ProjectID) != nil
|
||||
})
|
||||
}
|
||||
|
||||
func NewAppNameSearchQuery(method TextComparison, value string) (SearchQuery, error) {
|
||||
return NewTextQuery(AppColumnName, value, method)
|
||||
}
|
||||
|
||||
func NewAppStateSearchQuery(value domain.AppState) (SearchQuery, error) {
|
||||
return NewNumberQuery(AppColumnState, int(value), NumberEquals)
|
||||
}
|
||||
|
||||
func NewAppProjectIDSearchQuery(id string) (SearchQuery, error) {
|
||||
return NewTextQuery(AppColumnProjectID, id, TextEquals)
|
||||
}
|
||||
@@ -1089,7 +1177,7 @@ func (c sqlOIDCConfig) set(app *App) {
|
||||
if c.loginBaseURI.Valid {
|
||||
app.OIDCConfig.LoginBaseURI = &c.loginBaseURI.String
|
||||
}
|
||||
compliance := domain.GetOIDCCompliance(app.OIDCConfig.Version, app.OIDCConfig.AppType, app.OIDCConfig.GrantTypes, app.OIDCConfig.ResponseTypes, app.OIDCConfig.AuthMethodType, app.OIDCConfig.RedirectURIs)
|
||||
compliance := domain.GetOIDCCompliance(gu.Ptr(app.OIDCConfig.Version), gu.Ptr(app.OIDCConfig.AppType), app.OIDCConfig.GrantTypes, app.OIDCConfig.ResponseTypes, gu.Ptr(app.OIDCConfig.AuthMethodType), app.OIDCConfig.RedirectURIs)
|
||||
app.OIDCConfig.ComplianceProblems = compliance.Problems
|
||||
|
||||
var err error
|
||||
|
Reference in New Issue
Block a user