mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 10:27:33 +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:
@@ -36,6 +36,7 @@ import (
|
|||||||
internal_authz "github.com/zitadel/zitadel/internal/api/authz"
|
internal_authz "github.com/zitadel/zitadel/internal/api/authz"
|
||||||
action_v2_beta "github.com/zitadel/zitadel/internal/api/grpc/action/v2beta"
|
action_v2_beta "github.com/zitadel/zitadel/internal/api/grpc/action/v2beta"
|
||||||
"github.com/zitadel/zitadel/internal/api/grpc/admin"
|
"github.com/zitadel/zitadel/internal/api/grpc/admin"
|
||||||
|
app "github.com/zitadel/zitadel/internal/api/grpc/app/v2beta"
|
||||||
"github.com/zitadel/zitadel/internal/api/grpc/auth"
|
"github.com/zitadel/zitadel/internal/api/grpc/auth"
|
||||||
feature_v2 "github.com/zitadel/zitadel/internal/api/grpc/feature/v2"
|
feature_v2 "github.com/zitadel/zitadel/internal/api/grpc/feature/v2"
|
||||||
feature_v2beta "github.com/zitadel/zitadel/internal/api/grpc/feature/v2beta"
|
feature_v2beta "github.com/zitadel/zitadel/internal/api/grpc/feature/v2beta"
|
||||||
@@ -509,6 +510,10 @@ func startAPIs(
|
|||||||
if err := apis.RegisterService(ctx, debug_events.CreateServer(commands, queries)); err != nil {
|
if err := apis.RegisterService(ctx, debug_events.CreateServer(commands, queries)); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err := apis.RegisterService(ctx, app.CreateServer(commands, queries, permissionCheck)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
instanceInterceptor := middleware.InstanceInterceptor(queries, config.ExternalDomain, login.IgnoreInstanceEndpoints...)
|
instanceInterceptor := middleware.InstanceInterceptor(queries, config.ExternalDomain, login.IgnoreInstanceEndpoints...)
|
||||||
assetsCache := middleware.AssetsCacheInterceptor(config.AssetStorage.Cache.MaxAge, config.AssetStorage.Cache.SharedMaxAge)
|
assetsCache := middleware.AssetsCacheInterceptor(config.AssetStorage.Cache.MaxAge, config.AssetStorage.Cache.SharedMaxAge)
|
||||||
apis.RegisterHandlerOnPrefix(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.SystemAuthZ, config.InternalAuthZ, id.SonyFlakeGenerator(), store, queries, middleware.CallDurationHandler, instanceInterceptor.Handler, assetsCache.Handler, limitingAccessInterceptor.Handle))
|
apis.RegisterHandlerOnPrefix(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.SystemAuthZ, config.InternalAuthZ, id.SonyFlakeGenerator(), store, queries, middleware.CallDurationHandler, instanceInterceptor.Handler, assetsCache.Handler, limitingAccessInterceptor.Handle))
|
||||||
|
@@ -783,7 +783,7 @@ func (s *Server) getProjectsAndApps(ctx context.Context, org string) ([]*v1_pb.D
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, nil, nil, err
|
return nil, nil, nil, nil, nil, err
|
||||||
}
|
}
|
||||||
apps, err := s.query.SearchApps(ctx, &query.AppSearchQueries{Queries: []query.SearchQuery{appSearch}}, false)
|
apps, err := s.query.SearchApps(ctx, &query.AppSearchQueries{Queries: []query.SearchQuery{appSearch}}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, nil, nil, err
|
return nil, nil, nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
208
internal/api/grpc/app/v2beta/app.go
Normal file
208
internal/api/grpc/app/v2beta/app.go
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/api/grpc/app/v2beta/convert"
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
|
app "github.com/zitadel/zitadel/pkg/grpc/app/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) CreateApplication(ctx context.Context, req *app.CreateApplicationRequest) (*app.CreateApplicationResponse, error) {
|
||||||
|
switch t := req.GetCreationRequestType().(type) {
|
||||||
|
case *app.CreateApplicationRequest_ApiRequest:
|
||||||
|
apiApp, err := s.command.AddAPIApplication(ctx, convert.CreateAPIApplicationRequestToDomain(req.GetName(), req.GetProjectId(), req.GetId(), t.ApiRequest), "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &app.CreateApplicationResponse{
|
||||||
|
AppId: apiApp.AppID,
|
||||||
|
CreationDate: timestamppb.New(apiApp.ChangeDate),
|
||||||
|
CreationResponseType: &app.CreateApplicationResponse_ApiResponse{
|
||||||
|
ApiResponse: &app.CreateAPIApplicationResponse{
|
||||||
|
ClientId: apiApp.ClientID,
|
||||||
|
ClientSecret: apiApp.ClientSecretString,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case *app.CreateApplicationRequest_OidcRequest:
|
||||||
|
oidcAppRequest, err := convert.CreateOIDCAppRequestToDomain(req.GetName(), req.GetProjectId(), req.GetOidcRequest())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
oidcApp, err := s.command.AddOIDCApplication(ctx, oidcAppRequest, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &app.CreateApplicationResponse{
|
||||||
|
AppId: oidcApp.AppID,
|
||||||
|
CreationDate: timestamppb.New(oidcApp.ChangeDate),
|
||||||
|
CreationResponseType: &app.CreateApplicationResponse_OidcResponse{
|
||||||
|
OidcResponse: &app.CreateOIDCApplicationResponse{
|
||||||
|
ClientId: oidcApp.ClientID,
|
||||||
|
ClientSecret: oidcApp.ClientSecretString,
|
||||||
|
NoneCompliant: oidcApp.Compliance.NoneCompliant,
|
||||||
|
ComplianceProblems: convert.ComplianceProblemsToLocalizedMessages(oidcApp.Compliance.Problems),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
case *app.CreateApplicationRequest_SamlRequest:
|
||||||
|
samlAppRequest, err := convert.CreateSAMLAppRequestToDomain(req.GetName(), req.GetProjectId(), req.GetSamlRequest())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
samlApp, err := s.command.AddSAMLApplication(ctx, samlAppRequest, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &app.CreateApplicationResponse{
|
||||||
|
AppId: samlApp.AppID,
|
||||||
|
CreationDate: timestamppb.New(samlApp.ChangeDate),
|
||||||
|
CreationResponseType: &app.CreateApplicationResponse_SamlResponse{
|
||||||
|
SamlResponse: &app.CreateSAMLApplicationResponse{},
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
return nil, zerrors.ThrowInvalidArgument(nil, "APP-0iiN46", "unknown app type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) UpdateApplication(ctx context.Context, req *app.UpdateApplicationRequest) (*app.UpdateApplicationResponse, error) {
|
||||||
|
var changedTime time.Time
|
||||||
|
|
||||||
|
if name := strings.TrimSpace(req.GetName()); name != "" {
|
||||||
|
updatedDetails, err := s.command.UpdateApplicationName(
|
||||||
|
ctx,
|
||||||
|
req.GetProjectId(),
|
||||||
|
&domain.ChangeApp{
|
||||||
|
AppID: req.GetId(),
|
||||||
|
AppName: name,
|
||||||
|
},
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
changedTime = updatedDetails.EventDate
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t := req.GetUpdateRequestType().(type) {
|
||||||
|
case *app.UpdateApplicationRequest_ApiConfigurationRequest:
|
||||||
|
updatedAPIApp, err := s.command.UpdateAPIApplication(ctx, convert.UpdateAPIApplicationConfigurationRequestToDomain(req.GetId(), req.GetProjectId(), t.ApiConfigurationRequest), "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
changedTime = updatedAPIApp.ChangeDate
|
||||||
|
|
||||||
|
case *app.UpdateApplicationRequest_OidcConfigurationRequest:
|
||||||
|
oidcApp, err := convert.UpdateOIDCAppConfigRequestToDomain(req.GetId(), req.GetProjectId(), t.OidcConfigurationRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedOIDCApp, err := s.command.UpdateOIDCApplication(ctx, oidcApp, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
changedTime = updatedOIDCApp.ChangeDate
|
||||||
|
|
||||||
|
case *app.UpdateApplicationRequest_SamlConfigurationRequest:
|
||||||
|
samlApp, err := convert.UpdateSAMLAppConfigRequestToDomain(req.GetId(), req.GetProjectId(), t.SamlConfigurationRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedSAMLApp, err := s.command.UpdateSAMLApplication(ctx, samlApp, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
changedTime = updatedSAMLApp.ChangeDate
|
||||||
|
}
|
||||||
|
|
||||||
|
return &app.UpdateApplicationResponse{
|
||||||
|
ChangeDate: timestamppb.New(changedTime),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) DeleteApplication(ctx context.Context, req *app.DeleteApplicationRequest) (*app.DeleteApplicationResponse, error) {
|
||||||
|
details, err := s.command.RemoveApplication(ctx, req.GetProjectId(), req.GetId(), "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &app.DeleteApplicationResponse{
|
||||||
|
DeletionDate: timestamppb.New(details.EventDate),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) DeactivateApplication(ctx context.Context, req *app.DeactivateApplicationRequest) (*app.DeactivateApplicationResponse, error) {
|
||||||
|
details, err := s.command.DeactivateApplication(ctx, req.GetProjectId(), req.GetId(), "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &app.DeactivateApplicationResponse{
|
||||||
|
DeactivationDate: timestamppb.New(details.EventDate),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ReactivateApplication(ctx context.Context, req *app.ReactivateApplicationRequest) (*app.ReactivateApplicationResponse, error) {
|
||||||
|
details, err := s.command.ReactivateApplication(ctx, req.GetProjectId(), req.GetId(), "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &app.ReactivateApplicationResponse{
|
||||||
|
ReactivationDate: timestamppb.New(details.EventDate),
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) RegenerateClientSecret(ctx context.Context, req *app.RegenerateClientSecretRequest) (*app.RegenerateClientSecretResponse, error) {
|
||||||
|
var secret string
|
||||||
|
var changeDate time.Time
|
||||||
|
|
||||||
|
switch req.GetAppType().(type) {
|
||||||
|
case *app.RegenerateClientSecretRequest_IsApi:
|
||||||
|
config, err := s.command.ChangeAPIApplicationSecret(ctx, req.GetProjectId(), req.GetApplicationId(), "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
secret = config.ClientSecretString
|
||||||
|
changeDate = config.ChangeDate
|
||||||
|
|
||||||
|
case *app.RegenerateClientSecretRequest_IsOidc:
|
||||||
|
config, err := s.command.ChangeOIDCApplicationSecret(ctx, req.GetProjectId(), req.GetApplicationId(), "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
secret = config.ClientSecretString
|
||||||
|
changeDate = config.ChangeDate
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, zerrors.ThrowInvalidArgument(nil, "APP-aLWIzw", "unknown app type")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &app.RegenerateClientSecretResponse{
|
||||||
|
ClientSecret: secret,
|
||||||
|
CreationDate: timestamppb.New(changeDate),
|
||||||
|
}, nil
|
||||||
|
}
|
60
internal/api/grpc/app/v2beta/convert/api_app.go
Normal file
60
internal/api/grpc/app/v2beta/convert/api_app.go
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
package convert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||||
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
|
app "github.com/zitadel/zitadel/pkg/grpc/app/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateAPIApplicationRequestToDomain(name, projectID, appID string, app *app.CreateAPIApplicationRequest) *domain.APIApp {
|
||||||
|
return &domain.APIApp{
|
||||||
|
ObjectRoot: models.ObjectRoot{
|
||||||
|
AggregateID: projectID,
|
||||||
|
},
|
||||||
|
AppName: name,
|
||||||
|
AppID: appID,
|
||||||
|
AuthMethodType: apiAuthMethodTypeToDomain(app.GetAuthMethodType()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateAPIApplicationConfigurationRequestToDomain(appID, projectID string, app *app.UpdateAPIApplicationConfigurationRequest) *domain.APIApp {
|
||||||
|
return &domain.APIApp{
|
||||||
|
ObjectRoot: models.ObjectRoot{
|
||||||
|
AggregateID: projectID,
|
||||||
|
},
|
||||||
|
AppID: appID,
|
||||||
|
AuthMethodType: apiAuthMethodTypeToDomain(app.GetAuthMethodType()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func appAPIConfigToPb(apiApp *query.APIApp) app.ApplicationConfig {
|
||||||
|
return &app.Application_ApiConfig{
|
||||||
|
ApiConfig: &app.APIConfig{
|
||||||
|
ClientId: apiApp.ClientID,
|
||||||
|
AuthMethodType: apiAuthMethodTypeToPb(apiApp.AuthMethodType),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func apiAuthMethodTypeToDomain(authType app.APIAuthMethodType) domain.APIAuthMethodType {
|
||||||
|
switch authType {
|
||||||
|
case app.APIAuthMethodType_API_AUTH_METHOD_TYPE_BASIC:
|
||||||
|
return domain.APIAuthMethodTypeBasic
|
||||||
|
case app.APIAuthMethodType_API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT:
|
||||||
|
return domain.APIAuthMethodTypePrivateKeyJWT
|
||||||
|
default:
|
||||||
|
return domain.APIAuthMethodTypeBasic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func apiAuthMethodTypeToPb(methodType domain.APIAuthMethodType) app.APIAuthMethodType {
|
||||||
|
switch methodType {
|
||||||
|
case domain.APIAuthMethodTypeBasic:
|
||||||
|
return app.APIAuthMethodType_API_AUTH_METHOD_TYPE_BASIC
|
||||||
|
case domain.APIAuthMethodTypePrivateKeyJWT:
|
||||||
|
return app.APIAuthMethodType_API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT
|
||||||
|
default:
|
||||||
|
return app.APIAuthMethodType_API_AUTH_METHOD_TYPE_BASIC
|
||||||
|
}
|
||||||
|
}
|
149
internal/api/grpc/app/v2beta/convert/api_app_test.go
Normal file
149
internal/api/grpc/app/v2beta/convert/api_app_test.go
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
package convert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||||
|
app "github.com/zitadel/zitadel/pkg/grpc/app/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateAPIApplicationRequestToDomain(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
appName string
|
||||||
|
projectID string
|
||||||
|
appID string
|
||||||
|
req *app.CreateAPIApplicationRequest
|
||||||
|
want *domain.APIApp
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic auth method",
|
||||||
|
appName: "my-app",
|
||||||
|
projectID: "proj-1",
|
||||||
|
appID: "someID",
|
||||||
|
req: &app.CreateAPIApplicationRequest{
|
||||||
|
AuthMethodType: app.APIAuthMethodType_API_AUTH_METHOD_TYPE_BASIC,
|
||||||
|
},
|
||||||
|
want: &domain.APIApp{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "proj-1"},
|
||||||
|
AppName: "my-app",
|
||||||
|
AuthMethodType: domain.APIAuthMethodTypeBasic,
|
||||||
|
AppID: "someID",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "private key jwt",
|
||||||
|
appName: "jwt-app",
|
||||||
|
projectID: "proj-2",
|
||||||
|
req: &app.CreateAPIApplicationRequest{
|
||||||
|
AuthMethodType: app.APIAuthMethodType_API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT,
|
||||||
|
},
|
||||||
|
want: &domain.APIApp{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "proj-2"},
|
||||||
|
AppName: "jwt-app",
|
||||||
|
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
got := CreateAPIApplicationRequestToDomain(tt.appName, tt.projectID, tt.appID, tt.req)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateAPIApplicationConfigurationRequestToDomain(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
appID string
|
||||||
|
projectID string
|
||||||
|
req *app.UpdateAPIApplicationConfigurationRequest
|
||||||
|
want *domain.APIApp
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic auth method",
|
||||||
|
appID: "app-1",
|
||||||
|
projectID: "proj-1",
|
||||||
|
req: &app.UpdateAPIApplicationConfigurationRequest{
|
||||||
|
AuthMethodType: app.APIAuthMethodType_API_AUTH_METHOD_TYPE_BASIC,
|
||||||
|
},
|
||||||
|
want: &domain.APIApp{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "proj-1"},
|
||||||
|
AppID: "app-1",
|
||||||
|
AuthMethodType: domain.APIAuthMethodTypeBasic,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "private key jwt",
|
||||||
|
appID: "app-2",
|
||||||
|
projectID: "proj-2",
|
||||||
|
req: &app.UpdateAPIApplicationConfigurationRequest{
|
||||||
|
AuthMethodType: app.APIAuthMethodType_API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT,
|
||||||
|
},
|
||||||
|
want: &domain.APIApp{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "proj-2"},
|
||||||
|
AppID: "app-2",
|
||||||
|
AuthMethodType: domain.APIAuthMethodTypePrivateKeyJWT,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
got := UpdateAPIApplicationConfigurationRequestToDomain(tt.appID, tt.projectID, tt.req)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_apiAuthMethodTypeToPb(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
methodType domain.APIAuthMethodType
|
||||||
|
expectedResult app.APIAuthMethodType
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic auth method",
|
||||||
|
methodType: domain.APIAuthMethodTypeBasic,
|
||||||
|
expectedResult: app.APIAuthMethodType_API_AUTH_METHOD_TYPE_BASIC,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "private key jwt",
|
||||||
|
methodType: domain.APIAuthMethodTypePrivateKeyJWT,
|
||||||
|
expectedResult: app.APIAuthMethodType_API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown auth method defaults to basic",
|
||||||
|
expectedResult: app.APIAuthMethodType_API_AUTH_METHOD_TYPE_BASIC,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
res := apiAuthMethodTypeToPb(tc.methodType)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tc.expectedResult, res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
165
internal/api/grpc/app/v2beta/convert/convert.go
Normal file
165
internal/api/grpc/app/v2beta/convert/convert.go
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
package convert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"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/query"
|
||||||
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
|
app "github.com/zitadel/zitadel/pkg/grpc/app/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AppToPb(query_app *query.App) *app.Application {
|
||||||
|
if query_app == nil {
|
||||||
|
return &app.Application{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &app.Application{
|
||||||
|
Id: query_app.ID,
|
||||||
|
CreationDate: timestamppb.New(query_app.CreationDate),
|
||||||
|
ChangeDate: timestamppb.New(query_app.ChangeDate),
|
||||||
|
State: appStateToPb(query_app.State),
|
||||||
|
Name: query_app.Name,
|
||||||
|
Config: appConfigToPb(query_app),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AppsToPb(queryApps []*query.App) []*app.Application {
|
||||||
|
pbApps := make([]*app.Application, len(queryApps))
|
||||||
|
|
||||||
|
for i, queryApp := range queryApps {
|
||||||
|
pbApps[i] = AppToPb(queryApp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pbApps
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListApplicationsRequestToModel(sysDefaults systemdefaults.SystemDefaults, req *app.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
|
||||||
|
}
|
||||||
|
projectQuery, err := query.NewAppProjectIDSearchQuery(req.GetProjectId())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
queries = append(queries, projectQuery)
|
||||||
|
return &query.AppSearchQueries{
|
||||||
|
SearchRequest: query.SearchRequest{
|
||||||
|
Offset: offset,
|
||||||
|
Limit: limit,
|
||||||
|
Asc: asc,
|
||||||
|
SortingColumn: appSortingToColumn(req.GetSortingColumn()),
|
||||||
|
},
|
||||||
|
|
||||||
|
Queries: queries,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func appSortingToColumn(sortingCriteria app.AppSorting) query.Column {
|
||||||
|
switch sortingCriteria {
|
||||||
|
case app.AppSorting_APP_SORT_BY_CHANGE_DATE:
|
||||||
|
return query.AppColumnChangeDate
|
||||||
|
case app.AppSorting_APP_SORT_BY_CREATION_DATE:
|
||||||
|
return query.AppColumnCreationDate
|
||||||
|
case app.AppSorting_APP_SORT_BY_NAME:
|
||||||
|
return query.AppColumnName
|
||||||
|
case app.AppSorting_APP_SORT_BY_STATE:
|
||||||
|
return query.AppColumnState
|
||||||
|
case app.AppSorting_APP_SORT_BY_ID:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
return query.AppColumnID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func appStateToPb(state domain.AppState) app.AppState {
|
||||||
|
switch state {
|
||||||
|
case domain.AppStateActive:
|
||||||
|
return app.AppState_APP_STATE_ACTIVE
|
||||||
|
case domain.AppStateInactive:
|
||||||
|
return app.AppState_APP_STATE_INACTIVE
|
||||||
|
case domain.AppStateRemoved:
|
||||||
|
return app.AppState_APP_STATE_REMOVED
|
||||||
|
case domain.AppStateUnspecified:
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
return app.AppState_APP_STATE_UNSPECIFIED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func appConfigToPb(app *query.App) app.ApplicationConfig {
|
||||||
|
if app.OIDCConfig != nil {
|
||||||
|
return appOIDCConfigToPb(app.OIDCConfig)
|
||||||
|
}
|
||||||
|
if app.SAMLConfig != nil {
|
||||||
|
return appSAMLConfigToPb(app.SAMLConfig)
|
||||||
|
}
|
||||||
|
return appAPIConfigToPb(app.APIConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loginVersionToDomain(version *app.LoginVersion) (*domain.LoginVersion, *string, error) {
|
||||||
|
switch v := version.GetVersion().(type) {
|
||||||
|
case nil:
|
||||||
|
return gu.Ptr(domain.LoginVersionUnspecified), gu.Ptr(""), nil
|
||||||
|
case *app.LoginVersion_LoginV1:
|
||||||
|
return gu.Ptr(domain.LoginVersion1), gu.Ptr(""), nil
|
||||||
|
case *app.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) *app.LoginVersion {
|
||||||
|
switch version {
|
||||||
|
case domain.LoginVersionUnspecified:
|
||||||
|
return nil
|
||||||
|
case domain.LoginVersion1:
|
||||||
|
return &app.LoginVersion{Version: &app.LoginVersion_LoginV1{LoginV1: &app.LoginV1{}}}
|
||||||
|
case domain.LoginVersion2:
|
||||||
|
return &app.LoginVersion{Version: &app.LoginVersion_LoginV2{LoginV2: &app.LoginV2{BaseUri: baseURI}}}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func appQueriesToModel(queries []*app.ApplicationSearchFilter) (toReturn []query.SearchQuery, err error) {
|
||||||
|
toReturn = make([]query.SearchQuery, len(queries))
|
||||||
|
for i, query := range queries {
|
||||||
|
toReturn[i], err = appQueryToModel(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return toReturn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func appQueryToModel(appQuery *app.ApplicationSearchFilter) (query.SearchQuery, error) {
|
||||||
|
switch q := appQuery.GetFilter().(type) {
|
||||||
|
case *app.ApplicationSearchFilter_NameFilter:
|
||||||
|
return query.NewAppNameSearchQuery(filter.TextMethodPbToQuery(q.NameFilter.GetMethod()), q.NameFilter.Name)
|
||||||
|
case *app.ApplicationSearchFilter_StateFilter:
|
||||||
|
return query.NewAppStateSearchQuery(domain.AppState(q.StateFilter))
|
||||||
|
case *app.ApplicationSearchFilter_ApiAppOnly:
|
||||||
|
return query.NewNotNullQuery(query.AppAPIConfigColumnAppID)
|
||||||
|
case *app.ApplicationSearchFilter_OidcAppOnly:
|
||||||
|
return query.NewNotNullQuery(query.AppOIDCConfigColumnAppID)
|
||||||
|
case *app.ApplicationSearchFilter_SamlAppOnly:
|
||||||
|
return query.NewNotNullQuery(query.AppSAMLConfigColumnAppID)
|
||||||
|
default:
|
||||||
|
return nil, zerrors.ThrowInvalidArgument(nil, "CONV-z2mAGy", "List.Query.Invalid")
|
||||||
|
}
|
||||||
|
}
|
520
internal/api/grpc/app/v2beta/convert/convert_test.go
Normal file
520
internal/api/grpc/app/v2beta/convert/convert_test.go
Normal file
@@ -0,0 +1,520 @@
|
|||||||
|
package convert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
|
filter "github.com/zitadel/zitadel/internal/api/grpc/filter/v2beta"
|
||||||
|
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
|
app "github.com/zitadel/zitadel/pkg/grpc/app/v2beta"
|
||||||
|
filter_pb_v2 "github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||||
|
filter_pb_v2_beta "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAppToPb(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
testName string
|
||||||
|
inputQueryApp *query.App
|
||||||
|
expectedPbApp *app.Application
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "full app conversion",
|
||||||
|
inputQueryApp: &query.App{
|
||||||
|
ID: "id",
|
||||||
|
CreationDate: now,
|
||||||
|
ChangeDate: now,
|
||||||
|
State: domain.AppStateActive,
|
||||||
|
Name: "test-app",
|
||||||
|
APIConfig: &query.APIApp{},
|
||||||
|
},
|
||||||
|
expectedPbApp: &app.Application{
|
||||||
|
Id: "id",
|
||||||
|
CreationDate: timestamppb.New(now),
|
||||||
|
ChangeDate: timestamppb.New(now),
|
||||||
|
State: app.AppState_APP_STATE_ACTIVE,
|
||||||
|
Name: "test-app",
|
||||||
|
Config: &app.Application_ApiConfig{
|
||||||
|
ApiConfig: &app.APIConfig{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "nil app",
|
||||||
|
inputQueryApp: nil,
|
||||||
|
expectedPbApp: &app.Application{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
res := AppToPb(tc.inputQueryApp)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tc.expectedPbApp, res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListApplicationsRequestToModel(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
validSearchByNameQuery, err := query.NewAppNameSearchQuery(filter.TextMethodPbToQuery(filter_pb_v2_beta.TextFilterMethod_TEXT_FILTER_METHOD_EQUALS), "test")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
validSearchByProjectQuery, err := query.NewAppProjectIDSearchQuery("project1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
sysDefaults := systemdefaults.SystemDefaults{DefaultQueryLimit: 100, MaxQueryLimit: 150}
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
testName string
|
||||||
|
req *app.ListApplicationsRequest
|
||||||
|
|
||||||
|
expectedResponse *query.AppSearchQueries
|
||||||
|
expectedError error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "invalid pagination limit",
|
||||||
|
req: &app.ListApplicationsRequest{
|
||||||
|
Pagination: &filter_pb_v2.PaginationRequest{Asc: true, Limit: uint32(sysDefaults.MaxQueryLimit + 1)},
|
||||||
|
},
|
||||||
|
expectedResponse: nil,
|
||||||
|
expectedError: zerrors.ThrowInvalidArgumentf(fmt.Errorf("given: %d, allowed: %d", sysDefaults.MaxQueryLimit+1, sysDefaults.MaxQueryLimit), "QUERY-4M0fs", "Errors.Query.LimitExceeded"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "empty request",
|
||||||
|
req: &app.ListApplicationsRequest{
|
||||||
|
ProjectId: "project1",
|
||||||
|
Pagination: &filter_pb_v2.PaginationRequest{Asc: true},
|
||||||
|
},
|
||||||
|
expectedResponse: &query.AppSearchQueries{
|
||||||
|
SearchRequest: query.SearchRequest{
|
||||||
|
Offset: 0,
|
||||||
|
Limit: 100,
|
||||||
|
Asc: true,
|
||||||
|
SortingColumn: query.AppColumnID,
|
||||||
|
},
|
||||||
|
Queries: []query.SearchQuery{
|
||||||
|
validSearchByProjectQuery,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "valid request",
|
||||||
|
req: &app.ListApplicationsRequest{
|
||||||
|
ProjectId: "project1",
|
||||||
|
Filters: []*app.ApplicationSearchFilter{
|
||||||
|
{
|
||||||
|
Filter: &app.ApplicationSearchFilter_NameFilter{NameFilter: &app.ApplicationNameQuery{Name: "test"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
SortingColumn: app.AppSorting_APP_SORT_BY_NAME,
|
||||||
|
Pagination: &filter_pb_v2.PaginationRequest{Asc: true},
|
||||||
|
},
|
||||||
|
|
||||||
|
expectedResponse: &query.AppSearchQueries{
|
||||||
|
SearchRequest: query.SearchRequest{
|
||||||
|
Offset: 0,
|
||||||
|
Limit: 100,
|
||||||
|
Asc: true,
|
||||||
|
SortingColumn: query.AppColumnName,
|
||||||
|
},
|
||||||
|
Queries: []query.SearchQuery{
|
||||||
|
validSearchByNameQuery,
|
||||||
|
validSearchByProjectQuery,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
got, err := ListApplicationsRequestToModel(sysDefaults, tc.req)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tc.expectedError, err)
|
||||||
|
assert.Equal(t, tc.expectedResponse, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppSortingToColumn(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
sorting app.AppSorting
|
||||||
|
expected query.Column
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "sort by change date",
|
||||||
|
sorting: app.AppSorting_APP_SORT_BY_CHANGE_DATE,
|
||||||
|
expected: query.AppColumnChangeDate,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sort by creation date",
|
||||||
|
sorting: app.AppSorting_APP_SORT_BY_CREATION_DATE,
|
||||||
|
expected: query.AppColumnCreationDate,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sort by name",
|
||||||
|
sorting: app.AppSorting_APP_SORT_BY_NAME,
|
||||||
|
expected: query.AppColumnName,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sort by state",
|
||||||
|
sorting: app.AppSorting_APP_SORT_BY_STATE,
|
||||||
|
expected: query.AppColumnState,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sort by ID",
|
||||||
|
sorting: app.AppSorting_APP_SORT_BY_ID,
|
||||||
|
expected: query.AppColumnID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown sorting defaults to ID",
|
||||||
|
sorting: app.AppSorting(99),
|
||||||
|
expected: query.AppColumnID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
result := appSortingToColumn(tc.sorting)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tc.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppStateToPb(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
state domain.AppState
|
||||||
|
expected app.AppState
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "active state",
|
||||||
|
state: domain.AppStateActive,
|
||||||
|
expected: app.AppState_APP_STATE_ACTIVE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "inactive state",
|
||||||
|
state: domain.AppStateInactive,
|
||||||
|
expected: app.AppState_APP_STATE_INACTIVE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "removed state",
|
||||||
|
state: domain.AppStateRemoved,
|
||||||
|
expected: app.AppState_APP_STATE_REMOVED,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unspecified state",
|
||||||
|
state: domain.AppStateUnspecified,
|
||||||
|
expected: app.AppState_APP_STATE_UNSPECIFIED,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown state defaults to unspecified",
|
||||||
|
state: domain.AppState(99),
|
||||||
|
expected: app.AppState_APP_STATE_UNSPECIFIED,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
result := appStateToPb(tc.state)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tc.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppConfigToPb(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
app *query.App
|
||||||
|
expected app.ApplicationConfig
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "OIDC config",
|
||||||
|
app: &query.App{
|
||||||
|
OIDCConfig: &query.OIDCApp{},
|
||||||
|
},
|
||||||
|
expected: &app.Application_OidcConfig{
|
||||||
|
OidcConfig: &app.OIDCConfig{
|
||||||
|
ResponseTypes: []app.OIDCResponseType{},
|
||||||
|
GrantTypes: []app.OIDCGrantType{},
|
||||||
|
ComplianceProblems: []*app.OIDCLocalizedMessage{},
|
||||||
|
ClockSkew: &durationpb.Duration{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SAML config",
|
||||||
|
app: &query.App{
|
||||||
|
SAMLConfig: &query.SAMLApp{},
|
||||||
|
},
|
||||||
|
expected: &app.Application_SamlConfig{
|
||||||
|
SamlConfig: &app.SAMLConfig{
|
||||||
|
Metadata: &app.SAMLConfig_MetadataXml{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "API config",
|
||||||
|
app: &query.App{
|
||||||
|
APIConfig: &query.APIApp{},
|
||||||
|
},
|
||||||
|
expected: &app.Application_ApiConfig{
|
||||||
|
ApiConfig: &app.APIConfig{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
result := appConfigToPb(tc.app)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tc.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoginVersionToDomain(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
version *app.LoginVersion
|
||||||
|
expectedVer *domain.LoginVersion
|
||||||
|
expectedURI *string
|
||||||
|
expectedError error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil version",
|
||||||
|
version: nil,
|
||||||
|
expectedVer: gu.Ptr(domain.LoginVersionUnspecified),
|
||||||
|
expectedURI: gu.Ptr(""),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "login v1",
|
||||||
|
version: &app.LoginVersion{Version: &app.LoginVersion_LoginV1{LoginV1: &app.LoginV1{}}},
|
||||||
|
expectedVer: gu.Ptr(domain.LoginVersion1),
|
||||||
|
expectedURI: gu.Ptr(""),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "login v2 valid URI",
|
||||||
|
version: &app.LoginVersion{Version: &app.LoginVersion_LoginV2{LoginV2: &app.LoginV2{BaseUri: gu.Ptr("https://valid.url")}}},
|
||||||
|
expectedVer: gu.Ptr(domain.LoginVersion2),
|
||||||
|
expectedURI: gu.Ptr("https://valid.url"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "login v2 invalid URI",
|
||||||
|
version: &app.LoginVersion{Version: &app.LoginVersion_LoginV2{LoginV2: &app.LoginV2{BaseUri: gu.Ptr("://invalid")}}},
|
||||||
|
expectedVer: gu.Ptr(domain.LoginVersion2),
|
||||||
|
expectedURI: gu.Ptr("://invalid"),
|
||||||
|
expectedError: &url.Error{Op: "parse", URL: "://invalid", Err: errors.New("missing protocol scheme")},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown version type",
|
||||||
|
version: &app.LoginVersion{},
|
||||||
|
expectedVer: gu.Ptr(domain.LoginVersionUnspecified),
|
||||||
|
expectedURI: gu.Ptr(""),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
version, uri, err := loginVersionToDomain(tc.version)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tc.expectedVer, version)
|
||||||
|
assert.Equal(t, tc.expectedURI, uri)
|
||||||
|
assert.Equal(t, tc.expectedError, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLoginVersionToPb(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
version domain.LoginVersion
|
||||||
|
baseURI *string
|
||||||
|
expected *app.LoginVersion
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "unspecified version",
|
||||||
|
version: domain.LoginVersionUnspecified,
|
||||||
|
baseURI: nil,
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "login v1",
|
||||||
|
version: domain.LoginVersion1,
|
||||||
|
baseURI: nil,
|
||||||
|
expected: &app.LoginVersion{
|
||||||
|
Version: &app.LoginVersion_LoginV1{
|
||||||
|
LoginV1: &app.LoginV1{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "login v2",
|
||||||
|
version: domain.LoginVersion2,
|
||||||
|
baseURI: gu.Ptr("https://example.com"),
|
||||||
|
expected: &app.LoginVersion{
|
||||||
|
Version: &app.LoginVersion_LoginV2{
|
||||||
|
LoginV2: &app.LoginV2{
|
||||||
|
BaseUri: gu.Ptr("https://example.com"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown version",
|
||||||
|
version: domain.LoginVersion(99),
|
||||||
|
baseURI: nil,
|
||||||
|
expected: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
result := loginVersionToPb(tc.version, tc.baseURI)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tc.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppQueryToModel(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
validAppNameSearchQuery, err := query.NewAppNameSearchQuery(query.TextEquals, "test")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
validAppStateSearchQuery, err := query.NewAppStateSearchQuery(domain.AppStateActive)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
query *app.ApplicationSearchFilter
|
||||||
|
|
||||||
|
expectedQuery query.SearchQuery
|
||||||
|
expectedError error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "name query",
|
||||||
|
query: &app.ApplicationSearchFilter{
|
||||||
|
Filter: &app.ApplicationSearchFilter_NameFilter{
|
||||||
|
NameFilter: &app.ApplicationNameQuery{
|
||||||
|
Name: "test",
|
||||||
|
Method: filter_pb_v2.TextFilterMethod_TEXT_FILTER_METHOD_EQUALS,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedQuery: validAppNameSearchQuery,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "state query",
|
||||||
|
query: &app.ApplicationSearchFilter{
|
||||||
|
Filter: &app.ApplicationSearchFilter_StateFilter{
|
||||||
|
StateFilter: app.AppState_APP_STATE_ACTIVE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedQuery: validAppStateSearchQuery,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "api app only query",
|
||||||
|
query: &app.ApplicationSearchFilter{
|
||||||
|
Filter: &app.ApplicationSearchFilter_ApiAppOnly{},
|
||||||
|
},
|
||||||
|
expectedQuery: &query.NotNullQuery{
|
||||||
|
Column: query.AppAPIConfigColumnAppID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "oidc app only query",
|
||||||
|
query: &app.ApplicationSearchFilter{
|
||||||
|
Filter: &app.ApplicationSearchFilter_OidcAppOnly{},
|
||||||
|
},
|
||||||
|
expectedQuery: &query.NotNullQuery{
|
||||||
|
Column: query.AppOIDCConfigColumnAppID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "saml app only query",
|
||||||
|
query: &app.ApplicationSearchFilter{
|
||||||
|
Filter: &app.ApplicationSearchFilter_SamlAppOnly{},
|
||||||
|
},
|
||||||
|
expectedQuery: &query.NotNullQuery{
|
||||||
|
Column: query.AppSAMLConfigColumnAppID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid query type",
|
||||||
|
query: &app.ApplicationSearchFilter{},
|
||||||
|
expectedQuery: nil,
|
||||||
|
expectedError: zerrors.ThrowInvalidArgument(nil, "CONV-z2mAGy", "List.Query.Invalid"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
result, err := appQueryToModel(tc.query)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tc.expectedError, err)
|
||||||
|
assert.Equal(t, tc.expectedQuery, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
291
internal/api/grpc/app/v2beta/convert/oidc_app.go
Normal file
291
internal/api/grpc/app/v2beta/convert/oidc_app.go
Normal file
@@ -0,0 +1,291 @@
|
|||||||
|
package convert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||||
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
|
app "github.com/zitadel/zitadel/pkg/grpc/app/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateOIDCAppRequestToDomain(name, projectID string, req *app.CreateOIDCApplicationRequest) (*domain.OIDCApp, error) {
|
||||||
|
loginVersion, loginBaseURI, err := loginVersionToDomain(req.GetLoginVersion())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &domain.OIDCApp{
|
||||||
|
ObjectRoot: models.ObjectRoot{
|
||||||
|
AggregateID: projectID,
|
||||||
|
},
|
||||||
|
AppName: name,
|
||||||
|
OIDCVersion: gu.Ptr(domain.OIDCVersionV1),
|
||||||
|
RedirectUris: req.GetRedirectUris(),
|
||||||
|
ResponseTypes: oidcResponseTypesToDomain(req.GetResponseTypes()),
|
||||||
|
GrantTypes: oidcGrantTypesToDomain(req.GetGrantTypes()),
|
||||||
|
ApplicationType: gu.Ptr(oidcApplicationTypeToDomain(req.GetAppType())),
|
||||||
|
AuthMethodType: gu.Ptr(oidcAuthMethodTypeToDomain(req.GetAuthMethodType())),
|
||||||
|
PostLogoutRedirectUris: req.GetPostLogoutRedirectUris(),
|
||||||
|
DevMode: &req.DevMode,
|
||||||
|
AccessTokenType: gu.Ptr(oidcTokenTypeToDomain(req.GetAccessTokenType())),
|
||||||
|
AccessTokenRoleAssertion: gu.Ptr(req.GetAccessTokenRoleAssertion()),
|
||||||
|
IDTokenRoleAssertion: gu.Ptr(req.GetIdTokenRoleAssertion()),
|
||||||
|
IDTokenUserinfoAssertion: gu.Ptr(req.GetIdTokenUserinfoAssertion()),
|
||||||
|
ClockSkew: gu.Ptr(req.GetClockSkew().AsDuration()),
|
||||||
|
AdditionalOrigins: req.GetAdditionalOrigins(),
|
||||||
|
SkipNativeAppSuccessPage: gu.Ptr(req.GetSkipNativeAppSuccessPage()),
|
||||||
|
BackChannelLogoutURI: gu.Ptr(req.GetBackChannelLogoutUri()),
|
||||||
|
LoginVersion: loginVersion,
|
||||||
|
LoginBaseURI: loginBaseURI,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateOIDCAppConfigRequestToDomain(appID, projectID string, app *app.UpdateOIDCApplicationConfigurationRequest) (*domain.OIDCApp, error) {
|
||||||
|
loginVersion, loginBaseURI, err := loginVersionToDomain(app.GetLoginVersion())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &domain.OIDCApp{
|
||||||
|
ObjectRoot: models.ObjectRoot{
|
||||||
|
AggregateID: projectID,
|
||||||
|
},
|
||||||
|
AppID: appID,
|
||||||
|
RedirectUris: app.RedirectUris,
|
||||||
|
ResponseTypes: oidcResponseTypesToDomain(app.ResponseTypes),
|
||||||
|
GrantTypes: oidcGrantTypesToDomain(app.GrantTypes),
|
||||||
|
ApplicationType: oidcApplicationTypeToDomainPtr(app.AppType),
|
||||||
|
AuthMethodType: oidcAuthMethodTypeToDomainPtr(app.AuthMethodType),
|
||||||
|
PostLogoutRedirectUris: app.PostLogoutRedirectUris,
|
||||||
|
DevMode: app.DevMode,
|
||||||
|
AccessTokenType: oidcTokenTypeToDomainPtr(app.AccessTokenType),
|
||||||
|
AccessTokenRoleAssertion: app.AccessTokenRoleAssertion,
|
||||||
|
IDTokenRoleAssertion: app.IdTokenRoleAssertion,
|
||||||
|
IDTokenUserinfoAssertion: app.IdTokenUserinfoAssertion,
|
||||||
|
ClockSkew: gu.Ptr(app.GetClockSkew().AsDuration()),
|
||||||
|
AdditionalOrigins: app.AdditionalOrigins,
|
||||||
|
SkipNativeAppSuccessPage: app.SkipNativeAppSuccessPage,
|
||||||
|
BackChannelLogoutURI: app.BackChannelLogoutUri,
|
||||||
|
LoginVersion: loginVersion,
|
||||||
|
LoginBaseURI: loginBaseURI,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func oidcResponseTypesToDomain(responseTypes []app.OIDCResponseType) []domain.OIDCResponseType {
|
||||||
|
if len(responseTypes) == 0 {
|
||||||
|
return []domain.OIDCResponseType{domain.OIDCResponseTypeCode}
|
||||||
|
}
|
||||||
|
oidcResponseTypes := make([]domain.OIDCResponseType, len(responseTypes))
|
||||||
|
for i, responseType := range responseTypes {
|
||||||
|
switch responseType {
|
||||||
|
case app.OIDCResponseType_OIDC_RESPONSE_TYPE_UNSPECIFIED:
|
||||||
|
oidcResponseTypes[i] = domain.OIDCResponseTypeUnspecified
|
||||||
|
case app.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE:
|
||||||
|
oidcResponseTypes[i] = domain.OIDCResponseTypeCode
|
||||||
|
case app.OIDCResponseType_OIDC_RESPONSE_TYPE_ID_TOKEN:
|
||||||
|
oidcResponseTypes[i] = domain.OIDCResponseTypeIDToken
|
||||||
|
case app.OIDCResponseType_OIDC_RESPONSE_TYPE_ID_TOKEN_TOKEN:
|
||||||
|
oidcResponseTypes[i] = domain.OIDCResponseTypeIDTokenToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return oidcResponseTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
func oidcGrantTypesToDomain(grantTypes []app.OIDCGrantType) []domain.OIDCGrantType {
|
||||||
|
if len(grantTypes) == 0 {
|
||||||
|
return []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode}
|
||||||
|
}
|
||||||
|
oidcGrantTypes := make([]domain.OIDCGrantType, len(grantTypes))
|
||||||
|
for i, grantType := range grantTypes {
|
||||||
|
switch grantType {
|
||||||
|
case app.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE:
|
||||||
|
oidcGrantTypes[i] = domain.OIDCGrantTypeAuthorizationCode
|
||||||
|
case app.OIDCGrantType_OIDC_GRANT_TYPE_IMPLICIT:
|
||||||
|
oidcGrantTypes[i] = domain.OIDCGrantTypeImplicit
|
||||||
|
case app.OIDCGrantType_OIDC_GRANT_TYPE_REFRESH_TOKEN:
|
||||||
|
oidcGrantTypes[i] = domain.OIDCGrantTypeRefreshToken
|
||||||
|
case app.OIDCGrantType_OIDC_GRANT_TYPE_DEVICE_CODE:
|
||||||
|
oidcGrantTypes[i] = domain.OIDCGrantTypeDeviceCode
|
||||||
|
case app.OIDCGrantType_OIDC_GRANT_TYPE_TOKEN_EXCHANGE:
|
||||||
|
oidcGrantTypes[i] = domain.OIDCGrantTypeTokenExchange
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return oidcGrantTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
func oidcApplicationTypeToDomainPtr(appType *app.OIDCAppType) *domain.OIDCApplicationType {
|
||||||
|
if appType == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res := oidcApplicationTypeToDomain(*appType)
|
||||||
|
return &res
|
||||||
|
}
|
||||||
|
|
||||||
|
func oidcApplicationTypeToDomain(appType app.OIDCAppType) domain.OIDCApplicationType {
|
||||||
|
switch appType {
|
||||||
|
case app.OIDCAppType_OIDC_APP_TYPE_WEB:
|
||||||
|
return domain.OIDCApplicationTypeWeb
|
||||||
|
case app.OIDCAppType_OIDC_APP_TYPE_USER_AGENT:
|
||||||
|
return domain.OIDCApplicationTypeUserAgent
|
||||||
|
case app.OIDCAppType_OIDC_APP_TYPE_NATIVE:
|
||||||
|
return domain.OIDCApplicationTypeNative
|
||||||
|
}
|
||||||
|
return domain.OIDCApplicationTypeWeb
|
||||||
|
}
|
||||||
|
|
||||||
|
func oidcAuthMethodTypeToDomainPtr(authType *app.OIDCAuthMethodType) *domain.OIDCAuthMethodType {
|
||||||
|
if authType == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res := oidcAuthMethodTypeToDomain(*authType)
|
||||||
|
return &res
|
||||||
|
}
|
||||||
|
|
||||||
|
func oidcAuthMethodTypeToDomain(authType app.OIDCAuthMethodType) domain.OIDCAuthMethodType {
|
||||||
|
switch authType {
|
||||||
|
case app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC:
|
||||||
|
return domain.OIDCAuthMethodTypeBasic
|
||||||
|
case app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_POST:
|
||||||
|
return domain.OIDCAuthMethodTypePost
|
||||||
|
case app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE:
|
||||||
|
return domain.OIDCAuthMethodTypeNone
|
||||||
|
case app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT:
|
||||||
|
return domain.OIDCAuthMethodTypePrivateKeyJWT
|
||||||
|
default:
|
||||||
|
return domain.OIDCAuthMethodTypeBasic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func oidcTokenTypeToDomainPtr(tokenType *app.OIDCTokenType) *domain.OIDCTokenType {
|
||||||
|
if tokenType == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res := oidcTokenTypeToDomain(*tokenType)
|
||||||
|
return &res
|
||||||
|
}
|
||||||
|
|
||||||
|
func oidcTokenTypeToDomain(tokenType app.OIDCTokenType) domain.OIDCTokenType {
|
||||||
|
switch tokenType {
|
||||||
|
case app.OIDCTokenType_OIDC_TOKEN_TYPE_BEARER:
|
||||||
|
return domain.OIDCTokenTypeBearer
|
||||||
|
case app.OIDCTokenType_OIDC_TOKEN_TYPE_JWT:
|
||||||
|
return domain.OIDCTokenTypeJWT
|
||||||
|
default:
|
||||||
|
return domain.OIDCTokenTypeBearer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ComplianceProblemsToLocalizedMessages(complianceProblems []string) []*app.OIDCLocalizedMessage {
|
||||||
|
converted := make([]*app.OIDCLocalizedMessage, len(complianceProblems))
|
||||||
|
for i, p := range complianceProblems {
|
||||||
|
converted[i] = &app.OIDCLocalizedMessage{Key: p}
|
||||||
|
}
|
||||||
|
|
||||||
|
return converted
|
||||||
|
}
|
||||||
|
|
||||||
|
func appOIDCConfigToPb(oidcApp *query.OIDCApp) *app.Application_OidcConfig {
|
||||||
|
return &app.Application_OidcConfig{
|
||||||
|
OidcConfig: &app.OIDCConfig{
|
||||||
|
RedirectUris: oidcApp.RedirectURIs,
|
||||||
|
ResponseTypes: oidcResponseTypesFromModel(oidcApp.ResponseTypes),
|
||||||
|
GrantTypes: oidcGrantTypesFromModel(oidcApp.GrantTypes),
|
||||||
|
AppType: oidcApplicationTypeToPb(oidcApp.AppType),
|
||||||
|
ClientId: oidcApp.ClientID,
|
||||||
|
AuthMethodType: oidcAuthMethodTypeToPb(oidcApp.AuthMethodType),
|
||||||
|
PostLogoutRedirectUris: oidcApp.PostLogoutRedirectURIs,
|
||||||
|
Version: app.OIDCVersion_OIDC_VERSION_1_0,
|
||||||
|
NoneCompliant: len(oidcApp.ComplianceProblems) != 0,
|
||||||
|
ComplianceProblems: ComplianceProblemsToLocalizedMessages(oidcApp.ComplianceProblems),
|
||||||
|
DevMode: oidcApp.IsDevMode,
|
||||||
|
AccessTokenType: oidcTokenTypeToPb(oidcApp.AccessTokenType),
|
||||||
|
AccessTokenRoleAssertion: oidcApp.AssertAccessTokenRole,
|
||||||
|
IdTokenRoleAssertion: oidcApp.AssertIDTokenRole,
|
||||||
|
IdTokenUserinfoAssertion: oidcApp.AssertIDTokenUserinfo,
|
||||||
|
ClockSkew: durationpb.New(oidcApp.ClockSkew),
|
||||||
|
AdditionalOrigins: oidcApp.AdditionalOrigins,
|
||||||
|
AllowedOrigins: oidcApp.AllowedOrigins,
|
||||||
|
SkipNativeAppSuccessPage: oidcApp.SkipNativeAppSuccessPage,
|
||||||
|
BackChannelLogoutUri: oidcApp.BackChannelLogoutURI,
|
||||||
|
LoginVersion: loginVersionToPb(oidcApp.LoginVersion, oidcApp.LoginBaseURI),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func oidcResponseTypesFromModel(responseTypes []domain.OIDCResponseType) []app.OIDCResponseType {
|
||||||
|
oidcResponseTypes := make([]app.OIDCResponseType, len(responseTypes))
|
||||||
|
for i, responseType := range responseTypes {
|
||||||
|
switch responseType {
|
||||||
|
case domain.OIDCResponseTypeUnspecified:
|
||||||
|
oidcResponseTypes[i] = app.OIDCResponseType_OIDC_RESPONSE_TYPE_UNSPECIFIED
|
||||||
|
case domain.OIDCResponseTypeCode:
|
||||||
|
oidcResponseTypes[i] = app.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE
|
||||||
|
case domain.OIDCResponseTypeIDToken:
|
||||||
|
oidcResponseTypes[i] = app.OIDCResponseType_OIDC_RESPONSE_TYPE_ID_TOKEN
|
||||||
|
case domain.OIDCResponseTypeIDTokenToken:
|
||||||
|
oidcResponseTypes[i] = app.OIDCResponseType_OIDC_RESPONSE_TYPE_ID_TOKEN_TOKEN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return oidcResponseTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
func oidcGrantTypesFromModel(grantTypes []domain.OIDCGrantType) []app.OIDCGrantType {
|
||||||
|
oidcGrantTypes := make([]app.OIDCGrantType, len(grantTypes))
|
||||||
|
for i, grantType := range grantTypes {
|
||||||
|
switch grantType {
|
||||||
|
case domain.OIDCGrantTypeAuthorizationCode:
|
||||||
|
oidcGrantTypes[i] = app.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE
|
||||||
|
case domain.OIDCGrantTypeImplicit:
|
||||||
|
oidcGrantTypes[i] = app.OIDCGrantType_OIDC_GRANT_TYPE_IMPLICIT
|
||||||
|
case domain.OIDCGrantTypeRefreshToken:
|
||||||
|
oidcGrantTypes[i] = app.OIDCGrantType_OIDC_GRANT_TYPE_REFRESH_TOKEN
|
||||||
|
case domain.OIDCGrantTypeDeviceCode:
|
||||||
|
oidcGrantTypes[i] = app.OIDCGrantType_OIDC_GRANT_TYPE_DEVICE_CODE
|
||||||
|
case domain.OIDCGrantTypeTokenExchange:
|
||||||
|
oidcGrantTypes[i] = app.OIDCGrantType_OIDC_GRANT_TYPE_TOKEN_EXCHANGE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return oidcGrantTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
func oidcApplicationTypeToPb(appType domain.OIDCApplicationType) app.OIDCAppType {
|
||||||
|
switch appType {
|
||||||
|
case domain.OIDCApplicationTypeWeb:
|
||||||
|
return app.OIDCAppType_OIDC_APP_TYPE_WEB
|
||||||
|
case domain.OIDCApplicationTypeUserAgent:
|
||||||
|
return app.OIDCAppType_OIDC_APP_TYPE_USER_AGENT
|
||||||
|
case domain.OIDCApplicationTypeNative:
|
||||||
|
return app.OIDCAppType_OIDC_APP_TYPE_NATIVE
|
||||||
|
default:
|
||||||
|
return app.OIDCAppType_OIDC_APP_TYPE_WEB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func oidcAuthMethodTypeToPb(authType domain.OIDCAuthMethodType) app.OIDCAuthMethodType {
|
||||||
|
switch authType {
|
||||||
|
case domain.OIDCAuthMethodTypeBasic:
|
||||||
|
return app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC
|
||||||
|
case domain.OIDCAuthMethodTypePost:
|
||||||
|
return app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_POST
|
||||||
|
case domain.OIDCAuthMethodTypeNone:
|
||||||
|
return app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE
|
||||||
|
case domain.OIDCAuthMethodTypePrivateKeyJWT:
|
||||||
|
return app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT
|
||||||
|
default:
|
||||||
|
return app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func oidcTokenTypeToPb(tokenType domain.OIDCTokenType) app.OIDCTokenType {
|
||||||
|
switch tokenType {
|
||||||
|
case domain.OIDCTokenTypeBearer:
|
||||||
|
return app.OIDCTokenType_OIDC_TOKEN_TYPE_BEARER
|
||||||
|
case domain.OIDCTokenTypeJWT:
|
||||||
|
return app.OIDCTokenType_OIDC_TOKEN_TYPE_JWT
|
||||||
|
default:
|
||||||
|
return app.OIDCTokenType_OIDC_TOKEN_TYPE_BEARER
|
||||||
|
}
|
||||||
|
}
|
755
internal/api/grpc/app/v2beta/convert/oidc_app_test.go
Normal file
755
internal/api/grpc/app/v2beta/convert/oidc_app_test.go
Normal file
@@ -0,0 +1,755 @@
|
|||||||
|
package convert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||||
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
|
app "github.com/zitadel/zitadel/pkg/grpc/app/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCreateOIDCAppRequestToDomain(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
testName string
|
||||||
|
projectID string
|
||||||
|
req *app.CreateOIDCApplicationRequest
|
||||||
|
|
||||||
|
expectedModel *domain.OIDCApp
|
||||||
|
expectedError error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "unparsable login version 2 URL",
|
||||||
|
projectID: "pid",
|
||||||
|
req: &app.CreateOIDCApplicationRequest{
|
||||||
|
LoginVersion: &app.LoginVersion{Version: &app.LoginVersion_LoginV2{
|
||||||
|
LoginV2: &app.LoginV2{BaseUri: gu.Ptr("%+o")}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedModel: nil,
|
||||||
|
expectedError: &url.Error{
|
||||||
|
URL: "%+o",
|
||||||
|
Op: "parse",
|
||||||
|
Err: url.EscapeError("%+o"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "all fields set",
|
||||||
|
projectID: "project1",
|
||||||
|
req: &app.CreateOIDCApplicationRequest{
|
||||||
|
RedirectUris: []string{"https://redirect"},
|
||||||
|
ResponseTypes: []app.OIDCResponseType{app.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE},
|
||||||
|
GrantTypes: []app.OIDCGrantType{app.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE},
|
||||||
|
AppType: app.OIDCAppType_OIDC_APP_TYPE_WEB,
|
||||||
|
AuthMethodType: app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC,
|
||||||
|
PostLogoutRedirectUris: []string{"https://logout"},
|
||||||
|
DevMode: true,
|
||||||
|
AccessTokenType: app.OIDCTokenType_OIDC_TOKEN_TYPE_BEARER,
|
||||||
|
AccessTokenRoleAssertion: true,
|
||||||
|
IdTokenRoleAssertion: true,
|
||||||
|
IdTokenUserinfoAssertion: true,
|
||||||
|
ClockSkew: durationpb.New(5 * time.Second),
|
||||||
|
AdditionalOrigins: []string{"https://origin"},
|
||||||
|
SkipNativeAppSuccessPage: true,
|
||||||
|
BackChannelLogoutUri: "https://backchannel",
|
||||||
|
LoginVersion: &app.LoginVersion{Version: &app.LoginVersion_LoginV2{LoginV2: &app.LoginV2{
|
||||||
|
BaseUri: gu.Ptr("https://login"),
|
||||||
|
}}},
|
||||||
|
},
|
||||||
|
expectedModel: &domain.OIDCApp{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "project1"},
|
||||||
|
AppName: "all fields set",
|
||||||
|
OIDCVersion: gu.Ptr(domain.OIDCVersionV1),
|
||||||
|
RedirectUris: []string{"https://redirect"},
|
||||||
|
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||||
|
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||||
|
ApplicationType: gu.Ptr(domain.OIDCApplicationTypeWeb),
|
||||||
|
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypeBasic),
|
||||||
|
PostLogoutRedirectUris: []string{"https://logout"},
|
||||||
|
DevMode: gu.Ptr(true),
|
||||||
|
AccessTokenType: gu.Ptr(domain.OIDCTokenTypeBearer),
|
||||||
|
AccessTokenRoleAssertion: gu.Ptr(true),
|
||||||
|
IDTokenRoleAssertion: gu.Ptr(true),
|
||||||
|
IDTokenUserinfoAssertion: gu.Ptr(true),
|
||||||
|
ClockSkew: gu.Ptr(5 * time.Second),
|
||||||
|
AdditionalOrigins: []string{"https://origin"},
|
||||||
|
SkipNativeAppSuccessPage: gu.Ptr(true),
|
||||||
|
BackChannelLogoutURI: gu.Ptr("https://backchannel"),
|
||||||
|
LoginVersion: gu.Ptr(domain.LoginVersion2),
|
||||||
|
LoginBaseURI: gu.Ptr("https://login"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
res, err := CreateOIDCAppRequestToDomain(tc.testName, tc.projectID, tc.req)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tc.expectedError, err)
|
||||||
|
assert.Equal(t, tc.expectedModel, res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateOIDCAppConfigRequestToDomain(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
testName string
|
||||||
|
|
||||||
|
appID string
|
||||||
|
projectID string
|
||||||
|
req *app.UpdateOIDCApplicationConfigurationRequest
|
||||||
|
|
||||||
|
expectedModel *domain.OIDCApp
|
||||||
|
expectedError error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "unparsable login version 2 URL",
|
||||||
|
appID: "app1",
|
||||||
|
projectID: "pid",
|
||||||
|
req: &app.UpdateOIDCApplicationConfigurationRequest{
|
||||||
|
LoginVersion: &app.LoginVersion{Version: &app.LoginVersion_LoginV2{
|
||||||
|
LoginV2: &app.LoginV2{BaseUri: gu.Ptr("%+o")},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
expectedModel: nil,
|
||||||
|
expectedError: &url.Error{
|
||||||
|
URL: "%+o",
|
||||||
|
Op: "parse",
|
||||||
|
Err: url.EscapeError("%+o"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "successful Update",
|
||||||
|
appID: "app1",
|
||||||
|
projectID: "proj1",
|
||||||
|
req: &app.UpdateOIDCApplicationConfigurationRequest{
|
||||||
|
RedirectUris: []string{"https://redirect"},
|
||||||
|
ResponseTypes: []app.OIDCResponseType{app.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE},
|
||||||
|
GrantTypes: []app.OIDCGrantType{app.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE},
|
||||||
|
AppType: gu.Ptr(app.OIDCAppType_OIDC_APP_TYPE_WEB),
|
||||||
|
AuthMethodType: gu.Ptr(app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC),
|
||||||
|
PostLogoutRedirectUris: []string{"https://logout"},
|
||||||
|
DevMode: gu.Ptr(true),
|
||||||
|
AccessTokenType: gu.Ptr(app.OIDCTokenType_OIDC_TOKEN_TYPE_BEARER),
|
||||||
|
AccessTokenRoleAssertion: gu.Ptr(true),
|
||||||
|
IdTokenRoleAssertion: gu.Ptr(true),
|
||||||
|
IdTokenUserinfoAssertion: gu.Ptr(true),
|
||||||
|
ClockSkew: durationpb.New(5 * time.Second),
|
||||||
|
AdditionalOrigins: []string{"https://origin"},
|
||||||
|
SkipNativeAppSuccessPage: gu.Ptr(true),
|
||||||
|
BackChannelLogoutUri: gu.Ptr("https://backchannel"),
|
||||||
|
LoginVersion: &app.LoginVersion{Version: &app.LoginVersion_LoginV2{
|
||||||
|
LoginV2: &app.LoginV2{BaseUri: gu.Ptr("https://login")},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
expectedModel: &domain.OIDCApp{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "proj1"},
|
||||||
|
AppID: "app1",
|
||||||
|
RedirectUris: []string{"https://redirect"},
|
||||||
|
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||||
|
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||||
|
ApplicationType: gu.Ptr(domain.OIDCApplicationTypeWeb),
|
||||||
|
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypeBasic),
|
||||||
|
PostLogoutRedirectUris: []string{"https://logout"},
|
||||||
|
DevMode: gu.Ptr(true),
|
||||||
|
AccessTokenType: gu.Ptr(domain.OIDCTokenTypeBearer),
|
||||||
|
AccessTokenRoleAssertion: gu.Ptr(true),
|
||||||
|
IDTokenRoleAssertion: gu.Ptr(true),
|
||||||
|
IDTokenUserinfoAssertion: gu.Ptr(true),
|
||||||
|
ClockSkew: gu.Ptr(5 * time.Second),
|
||||||
|
AdditionalOrigins: []string{"https://origin"},
|
||||||
|
SkipNativeAppSuccessPage: gu.Ptr(true),
|
||||||
|
BackChannelLogoutURI: gu.Ptr("https://backchannel"),
|
||||||
|
LoginVersion: gu.Ptr(domain.LoginVersion2),
|
||||||
|
LoginBaseURI: gu.Ptr("https://login"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
got, err := UpdateOIDCAppConfigRequestToDomain(tc.appID, tc.projectID, tc.req)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tc.expectedError, err)
|
||||||
|
assert.Equal(t, tc.expectedModel, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOIDCResponseTypesToDomain(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
testName string
|
||||||
|
inputResponseType []app.OIDCResponseType
|
||||||
|
expectedResponse []domain.OIDCResponseType
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "empty response types",
|
||||||
|
inputResponseType: []app.OIDCResponseType{},
|
||||||
|
expectedResponse: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "all response types",
|
||||||
|
inputResponseType: []app.OIDCResponseType{
|
||||||
|
app.OIDCResponseType_OIDC_RESPONSE_TYPE_UNSPECIFIED,
|
||||||
|
app.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE,
|
||||||
|
app.OIDCResponseType_OIDC_RESPONSE_TYPE_ID_TOKEN,
|
||||||
|
app.OIDCResponseType_OIDC_RESPONSE_TYPE_ID_TOKEN_TOKEN,
|
||||||
|
},
|
||||||
|
expectedResponse: []domain.OIDCResponseType{
|
||||||
|
domain.OIDCResponseTypeUnspecified,
|
||||||
|
domain.OIDCResponseTypeCode,
|
||||||
|
domain.OIDCResponseTypeIDToken,
|
||||||
|
domain.OIDCResponseTypeIDTokenToken,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "single response type",
|
||||||
|
inputResponseType: []app.OIDCResponseType{
|
||||||
|
app.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE,
|
||||||
|
},
|
||||||
|
expectedResponse: []domain.OIDCResponseType{
|
||||||
|
domain.OIDCResponseTypeCode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
res := oidcResponseTypesToDomain(tc.inputResponseType)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tc.expectedResponse, res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOIDCGrantTypesToDomain(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
testName string
|
||||||
|
inputGrantType []app.OIDCGrantType
|
||||||
|
expectedGrants []domain.OIDCGrantType
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "empty grant types",
|
||||||
|
inputGrantType: []app.OIDCGrantType{},
|
||||||
|
expectedGrants: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "all grant types",
|
||||||
|
inputGrantType: []app.OIDCGrantType{
|
||||||
|
app.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE,
|
||||||
|
app.OIDCGrantType_OIDC_GRANT_TYPE_IMPLICIT,
|
||||||
|
app.OIDCGrantType_OIDC_GRANT_TYPE_REFRESH_TOKEN,
|
||||||
|
app.OIDCGrantType_OIDC_GRANT_TYPE_DEVICE_CODE,
|
||||||
|
app.OIDCGrantType_OIDC_GRANT_TYPE_TOKEN_EXCHANGE,
|
||||||
|
},
|
||||||
|
expectedGrants: []domain.OIDCGrantType{
|
||||||
|
domain.OIDCGrantTypeAuthorizationCode,
|
||||||
|
domain.OIDCGrantTypeImplicit,
|
||||||
|
domain.OIDCGrantTypeRefreshToken,
|
||||||
|
domain.OIDCGrantTypeDeviceCode,
|
||||||
|
domain.OIDCGrantTypeTokenExchange,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "single grant type",
|
||||||
|
inputGrantType: []app.OIDCGrantType{
|
||||||
|
app.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE,
|
||||||
|
},
|
||||||
|
expectedGrants: []domain.OIDCGrantType{
|
||||||
|
domain.OIDCGrantTypeAuthorizationCode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
res := oidcGrantTypesToDomain(tc.inputGrantType)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tc.expectedGrants, res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOIDCApplicationTypeToDomain(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
appType app.OIDCAppType
|
||||||
|
expected domain.OIDCApplicationType
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "web type",
|
||||||
|
appType: app.OIDCAppType_OIDC_APP_TYPE_WEB,
|
||||||
|
expected: domain.OIDCApplicationTypeWeb,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "user agent type",
|
||||||
|
appType: app.OIDCAppType_OIDC_APP_TYPE_USER_AGENT,
|
||||||
|
expected: domain.OIDCApplicationTypeUserAgent,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "native type",
|
||||||
|
appType: app.OIDCAppType_OIDC_APP_TYPE_NATIVE,
|
||||||
|
expected: domain.OIDCApplicationTypeNative,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unspecified type defaults to web",
|
||||||
|
expected: domain.OIDCApplicationTypeWeb,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
result := oidcApplicationTypeToDomain(tc.appType)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tc.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOIDCAuthMethodTypeToDomain(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
authType app.OIDCAuthMethodType
|
||||||
|
expectedResponse domain.OIDCAuthMethodType
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic auth type",
|
||||||
|
authType: app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC,
|
||||||
|
expectedResponse: domain.OIDCAuthMethodTypeBasic,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "post auth type",
|
||||||
|
authType: app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_POST,
|
||||||
|
expectedResponse: domain.OIDCAuthMethodTypePost,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "none auth type",
|
||||||
|
authType: app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE,
|
||||||
|
expectedResponse: domain.OIDCAuthMethodTypeNone,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "private key jwt auth type",
|
||||||
|
authType: app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT,
|
||||||
|
expectedResponse: domain.OIDCAuthMethodTypePrivateKeyJWT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unspecified auth type defaults to basic",
|
||||||
|
expectedResponse: domain.OIDCAuthMethodTypeBasic,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
res := oidcAuthMethodTypeToDomain(tc.authType)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tc.expectedResponse, res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOIDCTokenTypeToDomain(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
tokenType app.OIDCTokenType
|
||||||
|
expectedType domain.OIDCTokenType
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "bearer token type",
|
||||||
|
tokenType: app.OIDCTokenType_OIDC_TOKEN_TYPE_BEARER,
|
||||||
|
expectedType: domain.OIDCTokenTypeBearer,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "jwt token type",
|
||||||
|
tokenType: app.OIDCTokenType_OIDC_TOKEN_TYPE_JWT,
|
||||||
|
expectedType: domain.OIDCTokenTypeJWT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unspecified defaults to bearer",
|
||||||
|
expectedType: domain.OIDCTokenTypeBearer,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
result := oidcTokenTypeToDomain(tc.tokenType)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tc.expectedType, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestAppOIDCConfigToPb(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
input *query.OIDCApp
|
||||||
|
expected *app.Application_OidcConfig
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty config",
|
||||||
|
input: &query.OIDCApp{},
|
||||||
|
expected: &app.Application_OidcConfig{
|
||||||
|
OidcConfig: &app.OIDCConfig{
|
||||||
|
Version: app.OIDCVersion_OIDC_VERSION_1_0,
|
||||||
|
ComplianceProblems: []*app.OIDCLocalizedMessage{},
|
||||||
|
ClockSkew: durationpb.New(0),
|
||||||
|
ResponseTypes: []app.OIDCResponseType{},
|
||||||
|
GrantTypes: []app.OIDCGrantType{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "full config",
|
||||||
|
input: &query.OIDCApp{
|
||||||
|
RedirectURIs: []string{"https://example.com/callback"},
|
||||||
|
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||||
|
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||||
|
AppType: domain.OIDCApplicationTypeWeb,
|
||||||
|
ClientID: "client123",
|
||||||
|
AuthMethodType: domain.OIDCAuthMethodTypeBasic,
|
||||||
|
PostLogoutRedirectURIs: []string{"https://example.com/logout"},
|
||||||
|
ComplianceProblems: []string{"problem1", "problem2"},
|
||||||
|
IsDevMode: true,
|
||||||
|
AccessTokenType: domain.OIDCTokenTypeBearer,
|
||||||
|
AssertAccessTokenRole: true,
|
||||||
|
AssertIDTokenRole: true,
|
||||||
|
AssertIDTokenUserinfo: true,
|
||||||
|
ClockSkew: 5 * time.Second,
|
||||||
|
AdditionalOrigins: []string{"https://app.example.com"},
|
||||||
|
AllowedOrigins: []string{"https://allowed.example.com"},
|
||||||
|
SkipNativeAppSuccessPage: true,
|
||||||
|
BackChannelLogoutURI: "https://example.com/backchannel",
|
||||||
|
LoginVersion: domain.LoginVersion2,
|
||||||
|
LoginBaseURI: gu.Ptr("https://login.example.com"),
|
||||||
|
},
|
||||||
|
expected: &app.Application_OidcConfig{
|
||||||
|
OidcConfig: &app.OIDCConfig{
|
||||||
|
RedirectUris: []string{"https://example.com/callback"},
|
||||||
|
ResponseTypes: []app.OIDCResponseType{app.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE},
|
||||||
|
GrantTypes: []app.OIDCGrantType{app.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE},
|
||||||
|
AppType: app.OIDCAppType_OIDC_APP_TYPE_WEB,
|
||||||
|
ClientId: "client123",
|
||||||
|
AuthMethodType: app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC,
|
||||||
|
PostLogoutRedirectUris: []string{"https://example.com/logout"},
|
||||||
|
Version: app.OIDCVersion_OIDC_VERSION_1_0,
|
||||||
|
NoneCompliant: true,
|
||||||
|
ComplianceProblems: []*app.OIDCLocalizedMessage{
|
||||||
|
{Key: "problem1"},
|
||||||
|
{Key: "problem2"},
|
||||||
|
},
|
||||||
|
DevMode: true,
|
||||||
|
AccessTokenType: app.OIDCTokenType_OIDC_TOKEN_TYPE_BEARER,
|
||||||
|
AccessTokenRoleAssertion: true,
|
||||||
|
IdTokenRoleAssertion: true,
|
||||||
|
IdTokenUserinfoAssertion: true,
|
||||||
|
ClockSkew: durationpb.New(5 * time.Second),
|
||||||
|
AdditionalOrigins: []string{"https://app.example.com"},
|
||||||
|
AllowedOrigins: []string{"https://allowed.example.com"},
|
||||||
|
SkipNativeAppSuccessPage: true,
|
||||||
|
BackChannelLogoutUri: "https://example.com/backchannel",
|
||||||
|
LoginVersion: &app.LoginVersion{
|
||||||
|
Version: &app.LoginVersion_LoginV2{
|
||||||
|
LoginV2: &app.LoginV2{
|
||||||
|
BaseUri: gu.Ptr("https://login.example.com"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tt {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
result := appOIDCConfigToPb(tt.input)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOIDCResponseTypesFromModel(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
responseTypes []domain.OIDCResponseType
|
||||||
|
expected []app.OIDCResponseType
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty response types",
|
||||||
|
responseTypes: []domain.OIDCResponseType{},
|
||||||
|
expected: []app.OIDCResponseType{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all response types",
|
||||||
|
responseTypes: []domain.OIDCResponseType{
|
||||||
|
domain.OIDCResponseTypeUnspecified,
|
||||||
|
domain.OIDCResponseTypeCode,
|
||||||
|
domain.OIDCResponseTypeIDToken,
|
||||||
|
domain.OIDCResponseTypeIDTokenToken,
|
||||||
|
},
|
||||||
|
expected: []app.OIDCResponseType{
|
||||||
|
app.OIDCResponseType_OIDC_RESPONSE_TYPE_UNSPECIFIED,
|
||||||
|
app.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE,
|
||||||
|
app.OIDCResponseType_OIDC_RESPONSE_TYPE_ID_TOKEN,
|
||||||
|
app.OIDCResponseType_OIDC_RESPONSE_TYPE_ID_TOKEN_TOKEN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single response type",
|
||||||
|
responseTypes: []domain.OIDCResponseType{
|
||||||
|
domain.OIDCResponseTypeCode,
|
||||||
|
},
|
||||||
|
expected: []app.OIDCResponseType{
|
||||||
|
app.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
result := oidcResponseTypesFromModel(tc.responseTypes)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tc.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestOIDCGrantTypesFromModel(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
grantTypes []domain.OIDCGrantType
|
||||||
|
expected []app.OIDCGrantType
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty grant types",
|
||||||
|
grantTypes: []domain.OIDCGrantType{},
|
||||||
|
expected: []app.OIDCGrantType{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all grant types",
|
||||||
|
grantTypes: []domain.OIDCGrantType{
|
||||||
|
domain.OIDCGrantTypeAuthorizationCode,
|
||||||
|
domain.OIDCGrantTypeImplicit,
|
||||||
|
domain.OIDCGrantTypeRefreshToken,
|
||||||
|
domain.OIDCGrantTypeDeviceCode,
|
||||||
|
domain.OIDCGrantTypeTokenExchange,
|
||||||
|
},
|
||||||
|
expected: []app.OIDCGrantType{
|
||||||
|
app.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE,
|
||||||
|
app.OIDCGrantType_OIDC_GRANT_TYPE_IMPLICIT,
|
||||||
|
app.OIDCGrantType_OIDC_GRANT_TYPE_REFRESH_TOKEN,
|
||||||
|
app.OIDCGrantType_OIDC_GRANT_TYPE_DEVICE_CODE,
|
||||||
|
app.OIDCGrantType_OIDC_GRANT_TYPE_TOKEN_EXCHANGE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "single grant type",
|
||||||
|
grantTypes: []domain.OIDCGrantType{
|
||||||
|
domain.OIDCGrantTypeAuthorizationCode,
|
||||||
|
},
|
||||||
|
expected: []app.OIDCGrantType{
|
||||||
|
app.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
result := oidcGrantTypesFromModel(tc.grantTypes)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tc.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOIDCApplicationTypeToPb(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
appType domain.OIDCApplicationType
|
||||||
|
expected app.OIDCAppType
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "web type",
|
||||||
|
appType: domain.OIDCApplicationTypeWeb,
|
||||||
|
expected: app.OIDCAppType_OIDC_APP_TYPE_WEB,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "user agent type",
|
||||||
|
appType: domain.OIDCApplicationTypeUserAgent,
|
||||||
|
expected: app.OIDCAppType_OIDC_APP_TYPE_USER_AGENT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "native type",
|
||||||
|
appType: domain.OIDCApplicationTypeNative,
|
||||||
|
expected: app.OIDCAppType_OIDC_APP_TYPE_NATIVE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unspecified type defaults to web",
|
||||||
|
appType: domain.OIDCApplicationType(999), // Invalid value to trigger default case
|
||||||
|
expected: app.OIDCAppType_OIDC_APP_TYPE_WEB,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
result := oidcApplicationTypeToPb(tc.appType)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tc.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOIDCAuthMethodTypeToPb(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
authType domain.OIDCAuthMethodType
|
||||||
|
expected app.OIDCAuthMethodType
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic auth type",
|
||||||
|
authType: domain.OIDCAuthMethodTypeBasic,
|
||||||
|
expected: app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "post auth type",
|
||||||
|
authType: domain.OIDCAuthMethodTypePost,
|
||||||
|
expected: app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_POST,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "none auth type",
|
||||||
|
authType: domain.OIDCAuthMethodTypeNone,
|
||||||
|
expected: app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "private key jwt auth type",
|
||||||
|
authType: domain.OIDCAuthMethodTypePrivateKeyJWT,
|
||||||
|
expected: app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown auth type defaults to basic",
|
||||||
|
authType: domain.OIDCAuthMethodType(999),
|
||||||
|
expected: app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
result := oidcAuthMethodTypeToPb(tc.authType)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tc.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOIDCTokenTypeToPb(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
tokenType domain.OIDCTokenType
|
||||||
|
expected app.OIDCTokenType
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "bearer token type",
|
||||||
|
tokenType: domain.OIDCTokenTypeBearer,
|
||||||
|
expected: app.OIDCTokenType_OIDC_TOKEN_TYPE_BEARER,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "jwt token type",
|
||||||
|
tokenType: domain.OIDCTokenTypeJWT,
|
||||||
|
expected: app.OIDCTokenType_OIDC_TOKEN_TYPE_JWT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown token type defaults to bearer",
|
||||||
|
tokenType: domain.OIDCTokenType(999), // Invalid value to trigger default case
|
||||||
|
expected: app.OIDCTokenType_OIDC_TOKEN_TYPE_BEARER,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
result := oidcTokenTypeToPb(tc.tokenType)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tc.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
77
internal/api/grpc/app/v2beta/convert/saml_app.go
Normal file
77
internal/api/grpc/app/v2beta/convert/saml_app.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package convert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||||
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
|
app "github.com/zitadel/zitadel/pkg/grpc/app/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateSAMLAppRequestToDomain(name, projectID string, req *app.CreateSAMLApplicationRequest) (*domain.SAMLApp, error) {
|
||||||
|
loginVersion, loginBaseURI, err := loginVersionToDomain(req.GetLoginVersion())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &domain.SAMLApp{
|
||||||
|
ObjectRoot: models.ObjectRoot{
|
||||||
|
AggregateID: projectID,
|
||||||
|
},
|
||||||
|
AppName: name,
|
||||||
|
Metadata: req.GetMetadataXml(),
|
||||||
|
MetadataURL: gu.Ptr(req.GetMetadataUrl()),
|
||||||
|
LoginVersion: loginVersion,
|
||||||
|
LoginBaseURI: loginBaseURI,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateSAMLAppConfigRequestToDomain(appID, projectID string, app *app.UpdateSAMLApplicationConfigurationRequest) (*domain.SAMLApp, error) {
|
||||||
|
loginVersion, loginBaseURI, err := loginVersionToDomain(app.GetLoginVersion())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
metasXML, metasURL := metasToDomain(app.GetMetadata())
|
||||||
|
return &domain.SAMLApp{
|
||||||
|
ObjectRoot: models.ObjectRoot{
|
||||||
|
AggregateID: projectID,
|
||||||
|
},
|
||||||
|
AppID: appID,
|
||||||
|
Metadata: metasXML,
|
||||||
|
MetadataURL: metasURL,
|
||||||
|
LoginVersion: loginVersion,
|
||||||
|
LoginBaseURI: loginBaseURI,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func metasToDomain(metas app.MetaType) ([]byte, *string) {
|
||||||
|
switch t := metas.(type) {
|
||||||
|
case *app.UpdateSAMLApplicationConfigurationRequest_MetadataXml:
|
||||||
|
return t.MetadataXml, nil
|
||||||
|
case *app.UpdateSAMLApplicationConfigurationRequest_MetadataUrl:
|
||||||
|
return nil, &t.MetadataUrl
|
||||||
|
case nil:
|
||||||
|
return nil, nil
|
||||||
|
default:
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func appSAMLConfigToPb(samlApp *query.SAMLApp) app.ApplicationConfig {
|
||||||
|
if samlApp == nil {
|
||||||
|
return &app.Application_SamlConfig{
|
||||||
|
SamlConfig: &app.SAMLConfig{
|
||||||
|
Metadata: &app.SAMLConfig_MetadataXml{},
|
||||||
|
LoginVersion: &app.LoginVersion{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &app.Application_SamlConfig{
|
||||||
|
SamlConfig: &app.SAMLConfig{
|
||||||
|
Metadata: &app.SAMLConfig_MetadataXml{MetadataXml: samlApp.Metadata},
|
||||||
|
LoginVersion: loginVersionToPb(samlApp.LoginVersion, samlApp.LoginBaseURI),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
256
internal/api/grpc/app/v2beta/convert/saml_app_test.go
Normal file
256
internal/api/grpc/app/v2beta/convert/saml_app_test.go
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
package convert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/brianvoe/gofakeit/v6"
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||||
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
|
app "github.com/zitadel/zitadel/pkg/grpc/app/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
func samlMetadataGen(entityID string) []byte {
|
||||||
|
str := fmt.Sprintf(`<?xml version="1.0"?>
|
||||||
|
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
|
||||||
|
validUntil="2022-08-26T14:08:16Z"
|
||||||
|
cacheDuration="PT604800S"
|
||||||
|
entityID="%s">
|
||||||
|
<md:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
||||||
|
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
|
||||||
|
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
||||||
|
Location="https://test.com/saml/acs"
|
||||||
|
index="1" />
|
||||||
|
|
||||||
|
</md:SPSSODescriptor>
|
||||||
|
</md:EntityDescriptor>
|
||||||
|
`,
|
||||||
|
entityID)
|
||||||
|
|
||||||
|
return []byte(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateSAMLAppRequestToDomain(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
genMetaForValidRequest := samlMetadataGen(gofakeit.URL())
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
testName string
|
||||||
|
appName string
|
||||||
|
projectID string
|
||||||
|
req *app.CreateSAMLApplicationRequest
|
||||||
|
|
||||||
|
expectedResponse *domain.SAMLApp
|
||||||
|
expectedError error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "login version error",
|
||||||
|
appName: "test-app",
|
||||||
|
projectID: "proj-1",
|
||||||
|
req: &app.CreateSAMLApplicationRequest{
|
||||||
|
Metadata: &app.CreateSAMLApplicationRequest_MetadataXml{
|
||||||
|
MetadataXml: samlMetadataGen(gofakeit.URL()),
|
||||||
|
},
|
||||||
|
LoginVersion: &app.LoginVersion{
|
||||||
|
Version: &app.LoginVersion_LoginV2{
|
||||||
|
LoginV2: &app.LoginV2{BaseUri: gu.Ptr("%+o")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedError: &url.Error{
|
||||||
|
URL: "%+o",
|
||||||
|
Op: "parse",
|
||||||
|
Err: url.EscapeError("%+o"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "valid request",
|
||||||
|
appName: "test-app",
|
||||||
|
projectID: "proj-1",
|
||||||
|
req: &app.CreateSAMLApplicationRequest{
|
||||||
|
Metadata: &app.CreateSAMLApplicationRequest_MetadataXml{
|
||||||
|
MetadataXml: genMetaForValidRequest,
|
||||||
|
},
|
||||||
|
LoginVersion: nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
expectedResponse: &domain.SAMLApp{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "proj-1"},
|
||||||
|
AppName: "test-app",
|
||||||
|
Metadata: genMetaForValidRequest,
|
||||||
|
MetadataURL: gu.Ptr(""),
|
||||||
|
LoginVersion: gu.Ptr(domain.LoginVersionUnspecified),
|
||||||
|
LoginBaseURI: gu.Ptr(""),
|
||||||
|
State: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "nil request",
|
||||||
|
appName: "test-app",
|
||||||
|
projectID: "proj-1",
|
||||||
|
req: nil,
|
||||||
|
|
||||||
|
expectedResponse: &domain.SAMLApp{
|
||||||
|
AppName: "test-app",
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "proj-1"},
|
||||||
|
MetadataURL: gu.Ptr(""),
|
||||||
|
LoginVersion: gu.Ptr(domain.LoginVersionUnspecified),
|
||||||
|
LoginBaseURI: gu.Ptr(""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
res, err := CreateSAMLAppRequestToDomain(tc.appName, tc.projectID, tc.req)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tc.expectedError, err)
|
||||||
|
assert.Equal(t, tc.expectedResponse, res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestUpdateSAMLAppConfigRequestToDomain(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
genMetaForValidRequest := samlMetadataGen(gofakeit.URL())
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
testName string
|
||||||
|
appID string
|
||||||
|
projectID string
|
||||||
|
req *app.UpdateSAMLApplicationConfigurationRequest
|
||||||
|
|
||||||
|
expectedResponse *domain.SAMLApp
|
||||||
|
expectedError error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "login version error",
|
||||||
|
appID: "app-1",
|
||||||
|
projectID: "proj-1",
|
||||||
|
req: &app.UpdateSAMLApplicationConfigurationRequest{
|
||||||
|
Metadata: &app.UpdateSAMLApplicationConfigurationRequest_MetadataXml{
|
||||||
|
MetadataXml: samlMetadataGen(gofakeit.URL()),
|
||||||
|
},
|
||||||
|
LoginVersion: &app.LoginVersion{
|
||||||
|
Version: &app.LoginVersion_LoginV2{
|
||||||
|
LoginV2: &app.LoginV2{BaseUri: gu.Ptr("%+o")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedError: &url.Error{
|
||||||
|
URL: "%+o",
|
||||||
|
Op: "parse",
|
||||||
|
Err: url.EscapeError("%+o"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "valid request",
|
||||||
|
appID: "app-1",
|
||||||
|
projectID: "proj-1",
|
||||||
|
req: &app.UpdateSAMLApplicationConfigurationRequest{
|
||||||
|
Metadata: &app.UpdateSAMLApplicationConfigurationRequest_MetadataXml{
|
||||||
|
MetadataXml: genMetaForValidRequest,
|
||||||
|
},
|
||||||
|
LoginVersion: nil,
|
||||||
|
},
|
||||||
|
expectedResponse: &domain.SAMLApp{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "proj-1"},
|
||||||
|
AppID: "app-1",
|
||||||
|
Metadata: genMetaForValidRequest,
|
||||||
|
LoginVersion: gu.Ptr(domain.LoginVersionUnspecified),
|
||||||
|
LoginBaseURI: gu.Ptr(""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "nil request",
|
||||||
|
appID: "app-1",
|
||||||
|
projectID: "proj-1",
|
||||||
|
req: nil,
|
||||||
|
expectedResponse: &domain.SAMLApp{
|
||||||
|
ObjectRoot: models.ObjectRoot{AggregateID: "proj-1"},
|
||||||
|
AppID: "app-1",
|
||||||
|
LoginVersion: gu.Ptr(domain.LoginVersionUnspecified),
|
||||||
|
LoginBaseURI: gu.Ptr(""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
res, err := UpdateSAMLAppConfigRequestToDomain(tc.appID, tc.projectID, tc.req)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tc.expectedError, err)
|
||||||
|
assert.Equal(t, tc.expectedResponse, res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppSAMLConfigToPb(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
metadata := samlMetadataGen(gofakeit.URL())
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
name string
|
||||||
|
inputSAMLApp *query.SAMLApp
|
||||||
|
|
||||||
|
expectedPbApp app.ApplicationConfig
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid conversion",
|
||||||
|
inputSAMLApp: &query.SAMLApp{
|
||||||
|
Metadata: metadata,
|
||||||
|
LoginVersion: domain.LoginVersion2,
|
||||||
|
LoginBaseURI: gu.Ptr("https://example.com"),
|
||||||
|
},
|
||||||
|
expectedPbApp: &app.Application_SamlConfig{
|
||||||
|
SamlConfig: &app.SAMLConfig{
|
||||||
|
Metadata: &app.SAMLConfig_MetadataXml{
|
||||||
|
MetadataXml: metadata,
|
||||||
|
},
|
||||||
|
LoginVersion: &app.LoginVersion{
|
||||||
|
Version: &app.LoginVersion_LoginV2{
|
||||||
|
LoginV2: &app.LoginV2{BaseUri: gu.Ptr("https://example.com")},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil saml app",
|
||||||
|
inputSAMLApp: nil,
|
||||||
|
expectedPbApp: &app.Application_SamlConfig{
|
||||||
|
SamlConfig: &app.SAMLConfig{
|
||||||
|
Metadata: &app.SAMLConfig_MetadataXml{},
|
||||||
|
LoginVersion: &app.LoginVersion{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// When
|
||||||
|
got := appSAMLConfigToPb(tc.inputSAMLApp)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assert.Equal(t, tc.expectedPbApp, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
1446
internal/api/grpc/app/v2beta/integration_test/app_test.go
Normal file
1446
internal/api/grpc/app/v2beta/integration_test/app_test.go
Normal file
File diff suppressed because it is too large
Load Diff
575
internal/api/grpc/app/v2beta/integration_test/query_test.go
Normal file
575
internal/api/grpc/app/v2beta/integration_test/query_test.go
Normal file
@@ -0,0 +1,575 @@
|
|||||||
|
//go:build integration
|
||||||
|
|
||||||
|
package instance_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"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"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/integration"
|
||||||
|
app "github.com/zitadel/zitadel/pkg/grpc/app/v2beta"
|
||||||
|
"github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetApplication(t *testing.T) {
|
||||||
|
p, projectOwnerCtx := getProjectAndProjectContext(t, instance, IAMOwnerCtx)
|
||||||
|
|
||||||
|
apiAppName := gofakeit.AppName()
|
||||||
|
createdApiApp, errAPIAppCreation := instance.Client.AppV2Beta.CreateApplication(IAMOwnerCtx, &app.CreateApplicationRequest{
|
||||||
|
ProjectId: p.GetId(),
|
||||||
|
Name: apiAppName,
|
||||||
|
CreationRequestType: &app.CreateApplicationRequest_ApiRequest{
|
||||||
|
ApiRequest: &app.CreateAPIApplicationRequest{
|
||||||
|
AuthMethodType: app.APIAuthMethodType_API_AUTH_METHOD_TYPE_BASIC,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.Nil(t, errAPIAppCreation)
|
||||||
|
|
||||||
|
samlAppName := gofakeit.AppName()
|
||||||
|
createdSAMLApp, errSAMLAppCreation := instance.Client.AppV2Beta.CreateApplication(IAMOwnerCtx, &app.CreateApplicationRequest{
|
||||||
|
ProjectId: p.GetId(),
|
||||||
|
Name: samlAppName,
|
||||||
|
CreationRequestType: &app.CreateApplicationRequest_SamlRequest{
|
||||||
|
SamlRequest: &app.CreateSAMLApplicationRequest{
|
||||||
|
LoginVersion: &app.LoginVersion{Version: &app.LoginVersion_LoginV1{LoginV1: &app.LoginV1{}}},
|
||||||
|
Metadata: &app.CreateSAMLApplicationRequest_MetadataXml{MetadataXml: samlMetadataGen(gofakeit.URL())},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.Nil(t, errSAMLAppCreation)
|
||||||
|
|
||||||
|
oidcAppName := gofakeit.AppName()
|
||||||
|
createdOIDCApp, errOIDCAppCreation := instance.Client.AppV2Beta.CreateApplication(IAMOwnerCtx, &app.CreateApplicationRequest{
|
||||||
|
ProjectId: p.GetId(),
|
||||||
|
Name: oidcAppName,
|
||||||
|
CreationRequestType: &app.CreateApplicationRequest_OidcRequest{
|
||||||
|
OidcRequest: &app.CreateOIDCApplicationRequest{
|
||||||
|
RedirectUris: []string{"http://example.com"},
|
||||||
|
ResponseTypes: []app.OIDCResponseType{app.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE},
|
||||||
|
GrantTypes: []app.OIDCGrantType{app.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE},
|
||||||
|
AppType: app.OIDCAppType_OIDC_APP_TYPE_WEB,
|
||||||
|
AuthMethodType: app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC,
|
||||||
|
PostLogoutRedirectUris: []string{"http://example.com/home"},
|
||||||
|
Version: app.OIDCVersion_OIDC_VERSION_1_0,
|
||||||
|
AccessTokenType: app.OIDCTokenType_OIDC_TOKEN_TYPE_JWT,
|
||||||
|
BackChannelLogoutUri: "http://example.com/logout",
|
||||||
|
LoginVersion: &app.LoginVersion{Version: &app.LoginVersion_LoginV2{LoginV2: &app.LoginV2{BaseUri: &baseURI}}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.Nil(t, errOIDCAppCreation)
|
||||||
|
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
testName string
|
||||||
|
inputRequest *app.GetApplicationRequest
|
||||||
|
inputCtx context.Context
|
||||||
|
|
||||||
|
expectedErrorType codes.Code
|
||||||
|
expectedAppName string
|
||||||
|
expectedAppID string
|
||||||
|
expectedApplicationType string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "when unknown app ID should return not found error",
|
||||||
|
inputCtx: IAMOwnerCtx,
|
||||||
|
inputRequest: &app.GetApplicationRequest{
|
||||||
|
Id: gofakeit.Sentence(2),
|
||||||
|
},
|
||||||
|
|
||||||
|
expectedErrorType: codes.NotFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when user has no permission should return membership not found error",
|
||||||
|
inputCtx: NoPermissionCtx,
|
||||||
|
inputRequest: &app.GetApplicationRequest{
|
||||||
|
Id: createdApiApp.GetAppId(),
|
||||||
|
},
|
||||||
|
|
||||||
|
expectedErrorType: codes.NotFound,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when providing API app ID should return valid API app result",
|
||||||
|
inputCtx: projectOwnerCtx,
|
||||||
|
inputRequest: &app.GetApplicationRequest{
|
||||||
|
Id: createdApiApp.GetAppId(),
|
||||||
|
},
|
||||||
|
|
||||||
|
expectedAppName: apiAppName,
|
||||||
|
expectedAppID: createdApiApp.GetAppId(),
|
||||||
|
expectedApplicationType: fmt.Sprintf("%T", &app.Application_ApiConfig{}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when providing SAML app ID should return valid SAML app result",
|
||||||
|
inputCtx: IAMOwnerCtx,
|
||||||
|
inputRequest: &app.GetApplicationRequest{
|
||||||
|
Id: createdSAMLApp.GetAppId(),
|
||||||
|
},
|
||||||
|
|
||||||
|
expectedAppName: samlAppName,
|
||||||
|
expectedAppID: createdSAMLApp.GetAppId(),
|
||||||
|
expectedApplicationType: fmt.Sprintf("%T", &app.Application_SamlConfig{}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when providing OIDC app ID should return valid OIDC app result",
|
||||||
|
inputCtx: IAMOwnerCtx,
|
||||||
|
inputRequest: &app.GetApplicationRequest{
|
||||||
|
Id: createdOIDCApp.GetAppId(),
|
||||||
|
},
|
||||||
|
|
||||||
|
expectedAppName: oidcAppName,
|
||||||
|
expectedAppID: createdOIDCApp.GetAppId(),
|
||||||
|
expectedApplicationType: fmt.Sprintf("%T", &app.Application_OidcConfig{}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
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.GetApplication(tc.inputCtx, tc.inputRequest)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
require.Equal(t, tc.expectedErrorType, status.Code(err))
|
||||||
|
if tc.expectedErrorType == codes.OK {
|
||||||
|
|
||||||
|
assert.Equal(t, tc.expectedAppID, res.GetApp().GetId())
|
||||||
|
assert.Equal(t, tc.expectedAppName, res.GetApp().GetName())
|
||||||
|
assert.NotZero(t, res.GetApp().GetCreationDate())
|
||||||
|
assert.NotZero(t, res.GetApp().GetChangeDate())
|
||||||
|
|
||||||
|
appType := fmt.Sprintf("%T", res.GetApp().GetConfig())
|
||||||
|
assert.Equal(t, tc.expectedApplicationType, appType)
|
||||||
|
}
|
||||||
|
}, retryDuration, tick)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListApplications(t *testing.T) {
|
||||||
|
p, projectOwnerCtx := getProjectAndProjectContext(t, instance, IAMOwnerCtx)
|
||||||
|
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
createdApiApp, apiAppName := createAPIAppWithName(t, p.GetId())
|
||||||
|
|
||||||
|
createdDeactivatedApiApp, deactivatedApiAppName := createAPIAppWithName(t, p.GetId())
|
||||||
|
deactivateApp(t, createdDeactivatedApiApp, p.GetId())
|
||||||
|
|
||||||
|
_, createdSAMLApp, samlAppName := createSAMLAppWithName(t, gofakeit.URL(), p.GetId())
|
||||||
|
|
||||||
|
createdOIDCApp, oidcAppName := createOIDCAppWithName(t, gofakeit.URL(), p.GetId())
|
||||||
|
|
||||||
|
type appWithName struct {
|
||||||
|
app *app.CreateApplicationResponse
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sorting
|
||||||
|
appsSortedByName := []appWithName{
|
||||||
|
{name: apiAppName, app: createdApiApp},
|
||||||
|
{name: deactivatedApiAppName, app: createdDeactivatedApiApp},
|
||||||
|
{name: samlAppName, app: createdSAMLApp},
|
||||||
|
{name: oidcAppName, app: createdOIDCApp},
|
||||||
|
}
|
||||||
|
slices.SortFunc(appsSortedByName, func(a, b appWithName) int {
|
||||||
|
if a.name < b.name {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if a.name > b.name {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
|
||||||
|
appsSortedByID := []appWithName{
|
||||||
|
{name: apiAppName, app: createdApiApp},
|
||||||
|
{name: deactivatedApiAppName, app: createdDeactivatedApiApp},
|
||||||
|
{name: samlAppName, app: createdSAMLApp},
|
||||||
|
{name: oidcAppName, app: createdOIDCApp},
|
||||||
|
}
|
||||||
|
slices.SortFunc(appsSortedByID, func(a, b appWithName) int {
|
||||||
|
if a.app.GetAppId() < b.app.GetAppId() {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if a.app.GetAppId() > b.app.GetAppId() {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
|
||||||
|
appsSortedByCreationDate := []appWithName{
|
||||||
|
{name: apiAppName, app: createdApiApp},
|
||||||
|
{name: deactivatedApiAppName, app: createdDeactivatedApiApp},
|
||||||
|
{name: samlAppName, app: createdSAMLApp},
|
||||||
|
{name: oidcAppName, app: createdOIDCApp},
|
||||||
|
}
|
||||||
|
slices.SortFunc(appsSortedByCreationDate, func(a, b appWithName) int {
|
||||||
|
aCreationDate := a.app.GetCreationDate().AsTime()
|
||||||
|
bCreationDate := b.app.GetCreationDate().AsTime()
|
||||||
|
|
||||||
|
if aCreationDate.Before(bCreationDate) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
if bCreationDate.Before(aCreationDate) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
testName string
|
||||||
|
inputRequest *app.ListApplicationsRequest
|
||||||
|
inputCtx context.Context
|
||||||
|
|
||||||
|
expectedOrderedList []appWithName
|
||||||
|
expectedOrderedKeys func(keys []appWithName) any
|
||||||
|
actualOrderedKeys func(keys []*app.Application) any
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "when no apps found should return empty list",
|
||||||
|
inputCtx: IAMOwnerCtx,
|
||||||
|
inputRequest: &app.ListApplicationsRequest{
|
||||||
|
ProjectId: "another-id",
|
||||||
|
},
|
||||||
|
|
||||||
|
expectedOrderedList: []appWithName{},
|
||||||
|
expectedOrderedKeys: func(keys []appWithName) any { return keys },
|
||||||
|
actualOrderedKeys: func(keys []*app.Application) any { return keys },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when user has no read permission should return empty set",
|
||||||
|
inputCtx: NoPermissionCtx,
|
||||||
|
inputRequest: &app.ListApplicationsRequest{
|
||||||
|
ProjectId: p.GetId(),
|
||||||
|
},
|
||||||
|
|
||||||
|
expectedOrderedList: []appWithName{},
|
||||||
|
expectedOrderedKeys: func(keys []appWithName) any { return keys },
|
||||||
|
actualOrderedKeys: func(keys []*app.Application) any { return keys },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when sorting by name should return apps sorted by name in descending order",
|
||||||
|
inputCtx: IAMOwnerCtx,
|
||||||
|
inputRequest: &app.ListApplicationsRequest{
|
||||||
|
ProjectId: p.GetId(),
|
||||||
|
SortingColumn: app.AppSorting_APP_SORT_BY_NAME,
|
||||||
|
Pagination: &filter.PaginationRequest{Asc: true},
|
||||||
|
},
|
||||||
|
|
||||||
|
expectedOrderedList: appsSortedByName,
|
||||||
|
expectedOrderedKeys: func(apps []appWithName) any {
|
||||||
|
names := make([]string, len(apps))
|
||||||
|
for i, a := range apps {
|
||||||
|
names[i] = a.name
|
||||||
|
}
|
||||||
|
|
||||||
|
return names
|
||||||
|
},
|
||||||
|
actualOrderedKeys: func(apps []*app.Application) any {
|
||||||
|
names := make([]string, len(apps))
|
||||||
|
for i, a := range apps {
|
||||||
|
names[i] = a.GetName()
|
||||||
|
}
|
||||||
|
|
||||||
|
return names
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
testName: "when user is project owner should return apps sorted by name in ascending order",
|
||||||
|
inputCtx: projectOwnerCtx,
|
||||||
|
inputRequest: &app.ListApplicationsRequest{
|
||||||
|
ProjectId: p.GetId(),
|
||||||
|
SortingColumn: app.AppSorting_APP_SORT_BY_NAME,
|
||||||
|
Pagination: &filter.PaginationRequest{Asc: true},
|
||||||
|
},
|
||||||
|
|
||||||
|
expectedOrderedList: appsSortedByName,
|
||||||
|
expectedOrderedKeys: func(apps []appWithName) any {
|
||||||
|
names := make([]string, len(apps))
|
||||||
|
for i, a := range apps {
|
||||||
|
names[i] = a.name
|
||||||
|
}
|
||||||
|
|
||||||
|
return names
|
||||||
|
},
|
||||||
|
actualOrderedKeys: func(apps []*app.Application) any {
|
||||||
|
names := make([]string, len(apps))
|
||||||
|
for i, a := range apps {
|
||||||
|
names[i] = a.GetName()
|
||||||
|
}
|
||||||
|
|
||||||
|
return names
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
testName: "when sorting by id should return apps sorted by id in descending order",
|
||||||
|
inputCtx: IAMOwnerCtx,
|
||||||
|
inputRequest: &app.ListApplicationsRequest{
|
||||||
|
ProjectId: p.GetId(),
|
||||||
|
SortingColumn: app.AppSorting_APP_SORT_BY_ID,
|
||||||
|
Pagination: &filter.PaginationRequest{Asc: true},
|
||||||
|
},
|
||||||
|
expectedOrderedList: appsSortedByID,
|
||||||
|
expectedOrderedKeys: func(apps []appWithName) any {
|
||||||
|
ids := make([]string, len(apps))
|
||||||
|
for i, a := range apps {
|
||||||
|
ids[i] = a.app.GetAppId()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ids
|
||||||
|
},
|
||||||
|
actualOrderedKeys: func(apps []*app.Application) any {
|
||||||
|
ids := make([]string, len(apps))
|
||||||
|
for i, a := range apps {
|
||||||
|
ids[i] = a.GetId()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ids
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when sorting by creation date should return apps sorted by creation date in descending order",
|
||||||
|
inputCtx: IAMOwnerCtx,
|
||||||
|
inputRequest: &app.ListApplicationsRequest{
|
||||||
|
ProjectId: p.GetId(),
|
||||||
|
SortingColumn: app.AppSorting_APP_SORT_BY_CREATION_DATE,
|
||||||
|
Pagination: &filter.PaginationRequest{Asc: true},
|
||||||
|
},
|
||||||
|
expectedOrderedList: appsSortedByCreationDate,
|
||||||
|
expectedOrderedKeys: func(apps []appWithName) any {
|
||||||
|
creationDates := make([]time.Time, len(apps))
|
||||||
|
for i, a := range apps {
|
||||||
|
creationDates[i] = a.app.GetCreationDate().AsTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
return creationDates
|
||||||
|
},
|
||||||
|
actualOrderedKeys: func(apps []*app.Application) any {
|
||||||
|
creationDates := make([]time.Time, len(apps))
|
||||||
|
for i, a := range apps {
|
||||||
|
creationDates[i] = a.GetCreationDate().AsTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
return creationDates
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when filtering by active apps should return active apps only",
|
||||||
|
inputCtx: IAMOwnerCtx,
|
||||||
|
inputRequest: &app.ListApplicationsRequest{
|
||||||
|
ProjectId: p.GetId(),
|
||||||
|
Pagination: &filter.PaginationRequest{Asc: true},
|
||||||
|
Filters: []*app.ApplicationSearchFilter{
|
||||||
|
{Filter: &app.ApplicationSearchFilter_StateFilter{StateFilter: app.AppState_APP_STATE_ACTIVE}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedOrderedList: slices.DeleteFunc(
|
||||||
|
slices.Clone(appsSortedByID),
|
||||||
|
func(a appWithName) bool { return a.name == deactivatedApiAppName },
|
||||||
|
),
|
||||||
|
expectedOrderedKeys: func(apps []appWithName) any {
|
||||||
|
creationDates := make([]time.Time, len(apps))
|
||||||
|
for i, a := range apps {
|
||||||
|
creationDates[i] = a.app.GetCreationDate().AsTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
return creationDates
|
||||||
|
},
|
||||||
|
actualOrderedKeys: func(apps []*app.Application) any {
|
||||||
|
creationDates := make([]time.Time, len(apps))
|
||||||
|
for i, a := range apps {
|
||||||
|
creationDates[i] = a.GetCreationDate().AsTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
return creationDates
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when filtering by app type should return apps of matching type only",
|
||||||
|
inputCtx: IAMOwnerCtx,
|
||||||
|
inputRequest: &app.ListApplicationsRequest{
|
||||||
|
ProjectId: p.GetId(),
|
||||||
|
Pagination: &filter.PaginationRequest{Asc: true},
|
||||||
|
Filters: []*app.ApplicationSearchFilter{
|
||||||
|
{Filter: &app.ApplicationSearchFilter_OidcAppOnly{}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedOrderedList: slices.DeleteFunc(
|
||||||
|
slices.Clone(appsSortedByID),
|
||||||
|
func(a appWithName) bool { return a.name != oidcAppName },
|
||||||
|
),
|
||||||
|
expectedOrderedKeys: func(apps []appWithName) any {
|
||||||
|
creationDates := make([]time.Time, len(apps))
|
||||||
|
for i, a := range apps {
|
||||||
|
creationDates[i] = a.app.GetCreationDate().AsTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
return creationDates
|
||||||
|
},
|
||||||
|
actualOrderedKeys: func(apps []*app.Application) any {
|
||||||
|
creationDates := make([]time.Time, len(apps))
|
||||||
|
for i, a := range apps {
|
||||||
|
creationDates[i] = a.GetCreationDate().AsTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
return creationDates
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputCtx, 30*time.Second)
|
||||||
|
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||||
|
// When
|
||||||
|
res, err := instance.Client.AppV2Beta.ListApplications(tc.inputCtx, tc.inputRequest)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
require.Equal(ttt, codes.OK, status.Code(err))
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
assert.Len(ttt, res.GetApplications(), len(tc.expectedOrderedList))
|
||||||
|
actualOrderedKeys := tc.actualOrderedKeys(res.GetApplications())
|
||||||
|
expectedOrderedKeys := tc.expectedOrderedKeys(tc.expectedOrderedList)
|
||||||
|
assert.ElementsMatch(ttt, expectedOrderedKeys, actualOrderedKeys)
|
||||||
|
}
|
||||||
|
}, retryDuration, tick)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListApplications_WithPermissionV2(t *testing.T) {
|
||||||
|
ensureFeaturePermissionV2Enabled(t, instancePermissionV2)
|
||||||
|
iamOwnerCtx := instancePermissionV2.WithAuthorization(context.Background(), integration.UserTypeIAMOwner)
|
||||||
|
p, projectOwnerCtx := getProjectAndProjectContext(t, instancePermissionV2, iamOwnerCtx)
|
||||||
|
_, otherProjectOwnerCtx := getProjectAndProjectContext(t, instancePermissionV2, iamOwnerCtx)
|
||||||
|
|
||||||
|
appName1, appName2, appName3 := gofakeit.AppName(), gofakeit.AppName(), gofakeit.AppName()
|
||||||
|
reqForAPIAppCreation := &app.CreateApplicationRequest_ApiRequest{
|
||||||
|
ApiRequest: &app.CreateAPIApplicationRequest{AuthMethodType: app.APIAuthMethodType_API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT},
|
||||||
|
}
|
||||||
|
|
||||||
|
app1, appAPIConfigChangeErr := instancePermissionV2.Client.AppV2Beta.CreateApplication(iamOwnerCtx, &app.CreateApplicationRequest{
|
||||||
|
ProjectId: p.GetId(),
|
||||||
|
Name: appName1,
|
||||||
|
CreationRequestType: reqForAPIAppCreation,
|
||||||
|
})
|
||||||
|
require.Nil(t, appAPIConfigChangeErr)
|
||||||
|
|
||||||
|
app2, appAPIConfigChangeErr := instancePermissionV2.Client.AppV2Beta.CreateApplication(iamOwnerCtx, &app.CreateApplicationRequest{
|
||||||
|
ProjectId: p.GetId(),
|
||||||
|
Name: appName2,
|
||||||
|
CreationRequestType: reqForAPIAppCreation,
|
||||||
|
})
|
||||||
|
require.Nil(t, appAPIConfigChangeErr)
|
||||||
|
|
||||||
|
app3, appAPIConfigChangeErr := instancePermissionV2.Client.AppV2Beta.CreateApplication(iamOwnerCtx, &app.CreateApplicationRequest{
|
||||||
|
ProjectId: p.GetId(),
|
||||||
|
Name: appName3,
|
||||||
|
CreationRequestType: reqForAPIAppCreation,
|
||||||
|
})
|
||||||
|
require.Nil(t, appAPIConfigChangeErr)
|
||||||
|
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tt := []struct {
|
||||||
|
testName string
|
||||||
|
inputRequest *app.ListApplicationsRequest
|
||||||
|
inputCtx context.Context
|
||||||
|
|
||||||
|
expectedCode codes.Code
|
||||||
|
expectedAppIDs []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "when user has no read permission should return empty set",
|
||||||
|
inputCtx: instancePermissionV2.WithAuthorization(context.Background(), integration.UserTypeNoPermission),
|
||||||
|
inputRequest: &app.ListApplicationsRequest{
|
||||||
|
ProjectId: p.GetId(),
|
||||||
|
},
|
||||||
|
|
||||||
|
expectedAppIDs: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when projectOwner should return full app list",
|
||||||
|
inputCtx: projectOwnerCtx,
|
||||||
|
inputRequest: &app.ListApplicationsRequest{
|
||||||
|
ProjectId: p.GetId(),
|
||||||
|
},
|
||||||
|
|
||||||
|
expectedCode: codes.OK,
|
||||||
|
expectedAppIDs: []string{app1.GetAppId(), app2.GetAppId(), app3.GetAppId()},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when orgOwner should return full app list",
|
||||||
|
inputCtx: instancePermissionV2.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||||
|
inputRequest: &app.ListApplicationsRequest{
|
||||||
|
ProjectId: p.GetId(),
|
||||||
|
},
|
||||||
|
|
||||||
|
expectedAppIDs: []string{app1.GetAppId(), app2.GetAppId(), app3.GetAppId()},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when iamOwner user should return full app list",
|
||||||
|
inputCtx: iamOwnerCtx,
|
||||||
|
inputRequest: &app.ListApplicationsRequest{
|
||||||
|
ProjectId: p.GetId(),
|
||||||
|
},
|
||||||
|
|
||||||
|
expectedAppIDs: []string{app1.GetAppId(), app2.GetAppId(), app3.GetAppId()},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "when other projectOwner user should return empty list",
|
||||||
|
inputCtx: otherProjectOwnerCtx,
|
||||||
|
inputRequest: &app.ListApplicationsRequest{
|
||||||
|
ProjectId: p.GetId(),
|
||||||
|
},
|
||||||
|
|
||||||
|
expectedAppIDs: []string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tt {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputCtx, 5*time.Second)
|
||||||
|
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||||
|
// When
|
||||||
|
res, err := instancePermissionV2.Client.AppV2Beta.ListApplications(tc.inputCtx, tc.inputRequest)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
require.Equal(ttt, tc.expectedCode, status.Code(err))
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
require.Len(ttt, res.GetApplications(), len(tc.expectedAppIDs))
|
||||||
|
|
||||||
|
resAppIDs := []string{}
|
||||||
|
for _, a := range res.GetApplications() {
|
||||||
|
resAppIDs = append(resAppIDs, a.GetId())
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.ElementsMatch(ttt, tc.expectedAppIDs, resAppIDs)
|
||||||
|
}
|
||||||
|
}, retryDuration, tick)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
205
internal/api/grpc/app/v2beta/integration_test/server_test.go
Normal file
205
internal/api/grpc/app/v2beta/integration_test/server_test.go
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
//go:build integration
|
||||||
|
|
||||||
|
package instance_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/brianvoe/gofakeit/v6"
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/integration"
|
||||||
|
app "github.com/zitadel/zitadel/pkg/grpc/app/v2beta"
|
||||||
|
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||||
|
project_v2beta "github.com/zitadel/zitadel/pkg/grpc/project/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
NoPermissionCtx context.Context
|
||||||
|
LoginUserCtx context.Context
|
||||||
|
OrgOwnerCtx context.Context
|
||||||
|
IAMOwnerCtx context.Context
|
||||||
|
|
||||||
|
instance *integration.Instance
|
||||||
|
instancePermissionV2 *integration.Instance
|
||||||
|
|
||||||
|
baseURI = "http://example.com"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
os.Exit(func() int {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
instance = integration.NewInstance(ctx)
|
||||||
|
instancePermissionV2 = integration.NewInstance(ctx)
|
||||||
|
|
||||||
|
IAMOwnerCtx = instance.WithAuthorization(ctx, integration.UserTypeIAMOwner)
|
||||||
|
|
||||||
|
LoginUserCtx = instance.WithAuthorization(ctx, integration.UserTypeLogin)
|
||||||
|
OrgOwnerCtx = instance.WithAuthorization(ctx, integration.UserTypeOrgOwner)
|
||||||
|
NoPermissionCtx = instance.WithAuthorization(ctx, integration.UserTypeNoPermission)
|
||||||
|
|
||||||
|
return m.Run()
|
||||||
|
}())
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProjectAndProjectContext(t *testing.T, inst *integration.Instance, ctx context.Context) (*project_v2beta.CreateProjectResponse, context.Context) {
|
||||||
|
project := inst.CreateProject(ctx, t, inst.DefaultOrg.GetId(), gofakeit.Name(), false, false)
|
||||||
|
userResp := inst.CreateMachineUser(ctx)
|
||||||
|
patResp := inst.CreatePersonalAccessToken(ctx, userResp.GetUserId())
|
||||||
|
inst.CreateProjectMembership(t, ctx, project.GetId(), userResp.GetUserId())
|
||||||
|
projectOwnerCtx := integration.WithAuthorizationToken(context.Background(), patResp.Token)
|
||||||
|
|
||||||
|
return project, projectOwnerCtx
|
||||||
|
}
|
||||||
|
|
||||||
|
func samlMetadataGen(entityID string) []byte {
|
||||||
|
str := fmt.Sprintf(`<?xml version="1.0"?>
|
||||||
|
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
|
||||||
|
validUntil="2022-08-26T14:08:16Z"
|
||||||
|
cacheDuration="PT604800S"
|
||||||
|
entityID="%s">
|
||||||
|
<md:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
|
||||||
|
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
|
||||||
|
<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
||||||
|
Location="https://test.com/saml/acs"
|
||||||
|
index="1" />
|
||||||
|
|
||||||
|
</md:SPSSODescriptor>
|
||||||
|
</md:EntityDescriptor>
|
||||||
|
`,
|
||||||
|
entityID)
|
||||||
|
|
||||||
|
return []byte(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSAMLAppWithName(t *testing.T, baseURI, projectID string) ([]byte, *app.CreateApplicationResponse, string) {
|
||||||
|
samlMetas := samlMetadataGen(gofakeit.URL())
|
||||||
|
appName := gofakeit.AppName()
|
||||||
|
|
||||||
|
appForSAMLConfigChange, appSAMLConfigChangeErr := instance.Client.AppV2Beta.CreateApplication(IAMOwnerCtx, &app.CreateApplicationRequest{
|
||||||
|
ProjectId: projectID,
|
||||||
|
Name: appName,
|
||||||
|
CreationRequestType: &app.CreateApplicationRequest_SamlRequest{
|
||||||
|
SamlRequest: &app.CreateSAMLApplicationRequest{
|
||||||
|
Metadata: &app.CreateSAMLApplicationRequest_MetadataXml{
|
||||||
|
MetadataXml: samlMetas,
|
||||||
|
},
|
||||||
|
LoginVersion: &app.LoginVersion{
|
||||||
|
Version: &app.LoginVersion_LoginV2{
|
||||||
|
LoginV2: &app.LoginV2{
|
||||||
|
BaseUri: &baseURI,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.Nil(t, appSAMLConfigChangeErr)
|
||||||
|
|
||||||
|
return samlMetas, appForSAMLConfigChange, appName
|
||||||
|
}
|
||||||
|
|
||||||
|
func createSAMLApp(t *testing.T, baseURI, projectID string) ([]byte, *app.CreateApplicationResponse) {
|
||||||
|
metas, app, _ := createSAMLAppWithName(t, baseURI, projectID)
|
||||||
|
return metas, app
|
||||||
|
}
|
||||||
|
|
||||||
|
func createOIDCAppWithName(t *testing.T, baseURI, projectID string) (*app.CreateApplicationResponse, string) {
|
||||||
|
appName := gofakeit.AppName()
|
||||||
|
|
||||||
|
appForOIDCConfigChange, appOIDCConfigChangeErr := instance.Client.AppV2Beta.CreateApplication(IAMOwnerCtx, &app.CreateApplicationRequest{
|
||||||
|
ProjectId: projectID,
|
||||||
|
Name: appName,
|
||||||
|
CreationRequestType: &app.CreateApplicationRequest_OidcRequest{
|
||||||
|
OidcRequest: &app.CreateOIDCApplicationRequest{
|
||||||
|
RedirectUris: []string{"http://example.com"},
|
||||||
|
ResponseTypes: []app.OIDCResponseType{app.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE},
|
||||||
|
GrantTypes: []app.OIDCGrantType{app.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE},
|
||||||
|
AppType: app.OIDCAppType_OIDC_APP_TYPE_WEB,
|
||||||
|
AuthMethodType: app.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC,
|
||||||
|
PostLogoutRedirectUris: []string{"http://example.com/home"},
|
||||||
|
Version: app.OIDCVersion_OIDC_VERSION_1_0,
|
||||||
|
AccessTokenType: app.OIDCTokenType_OIDC_TOKEN_TYPE_JWT,
|
||||||
|
BackChannelLogoutUri: "http://example.com/logout",
|
||||||
|
LoginVersion: &app.LoginVersion{
|
||||||
|
Version: &app.LoginVersion_LoginV2{
|
||||||
|
LoginV2: &app.LoginV2{
|
||||||
|
BaseUri: &baseURI,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.Nil(t, appOIDCConfigChangeErr)
|
||||||
|
|
||||||
|
return appForOIDCConfigChange, appName
|
||||||
|
}
|
||||||
|
|
||||||
|
func createOIDCApp(t *testing.T, baseURI, projctID string) *app.CreateApplicationResponse {
|
||||||
|
app, _ := createOIDCAppWithName(t, baseURI, projctID)
|
||||||
|
|
||||||
|
return app
|
||||||
|
}
|
||||||
|
|
||||||
|
func createAPIAppWithName(t *testing.T, 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{
|
||||||
|
ProjectId: projectID,
|
||||||
|
Name: appName,
|
||||||
|
CreationRequestType: reqForAPIAppCreation,
|
||||||
|
})
|
||||||
|
require.Nil(t, appAPIConfigChangeErr)
|
||||||
|
|
||||||
|
return appForAPIConfigChange, appName
|
||||||
|
}
|
||||||
|
|
||||||
|
func createAPIApp(t *testing.T, projectID string) *app.CreateApplicationResponse {
|
||||||
|
res, _ := createAPIAppWithName(t, projectID)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func deactivateApp(t *testing.T, appToDeactivate *app.CreateApplicationResponse, projectID string) {
|
||||||
|
_, appDeactivateErr := instance.Client.AppV2Beta.DeactivateApplication(IAMOwnerCtx, &app.DeactivateApplicationRequest{
|
||||||
|
ProjectId: projectID,
|
||||||
|
Id: appToDeactivate.GetAppId(),
|
||||||
|
})
|
||||||
|
require.Nil(t, appDeactivateErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureFeaturePermissionV2Enabled(t *testing.T, instance *integration.Instance) {
|
||||||
|
ctx := instance.WithAuthorization(context.Background(), integration.UserTypeIAMOwner)
|
||||||
|
f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{
|
||||||
|
Inheritance: true,
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
if f.PermissionCheckV2.GetEnabled() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = instance.Client.FeatureV2.SetInstanceFeatures(ctx, &feature.SetInstanceFeaturesRequest{
|
||||||
|
PermissionCheckV2: gu.Ptr(true),
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(ctx, 5*time.Minute)
|
||||||
|
require.EventuallyWithT(t, func(tt *assert.CollectT) {
|
||||||
|
f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{Inheritance: true})
|
||||||
|
require.NoError(tt, err)
|
||||||
|
assert.True(tt, f.PermissionCheckV2.GetEnabled())
|
||||||
|
}, retryDuration, tick, "timed out waiting for ensuring instance feature")
|
||||||
|
}
|
37
internal/api/grpc/app/v2beta/query.go
Normal file
37
internal/api/grpc/app/v2beta/query.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/api/grpc/app/v2beta/convert"
|
||||||
|
filter "github.com/zitadel/zitadel/internal/api/grpc/filter/v2"
|
||||||
|
app "github.com/zitadel/zitadel/pkg/grpc/app/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) GetApplication(ctx context.Context, req *app.GetApplicationRequest) (*app.GetApplicationResponse, error) {
|
||||||
|
res, err := s.query.AppByIDWithPermission(ctx, req.GetId(), false, s.checkPermission)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &app.GetApplicationResponse{
|
||||||
|
App: convert.AppToPb(res),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) ListApplications(ctx context.Context, req *app.ListApplicationsRequest) (*app.ListApplicationsResponse, error) {
|
||||||
|
queries, err := convert.ListApplicationsRequestToModel(s.systemDefaults, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := s.query.SearchApps(ctx, queries, s.checkPermission)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &app.ListApplicationsResponse{
|
||||||
|
Applications: convert.AppsToPb(res.Apps),
|
||||||
|
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, res.SearchResponse),
|
||||||
|
}, nil
|
||||||
|
}
|
57
internal/api/grpc/app/v2beta/server.go
Normal file
57
internal/api/grpc/app/v2beta/server.go
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
|
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
||||||
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
|
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
|
app "github.com/zitadel/zitadel/pkg/grpc/app/v2beta"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ app.AppServiceServer = (*Server)(nil)
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
app.UnimplementedAppServiceServer
|
||||||
|
command *command.Commands
|
||||||
|
query *query.Queries
|
||||||
|
systemDefaults systemdefaults.SystemDefaults
|
||||||
|
checkPermission domain.PermissionCheck
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct{}
|
||||||
|
|
||||||
|
func CreateServer(
|
||||||
|
command *command.Commands,
|
||||||
|
query *query.Queries,
|
||||||
|
checkPermission domain.PermissionCheck,
|
||||||
|
) *Server {
|
||||||
|
return &Server{
|
||||||
|
command: command,
|
||||||
|
query: query,
|
||||||
|
checkPermission: checkPermission,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) RegisterServer(grpcServer *grpc.Server) {
|
||||||
|
app.RegisterAppServiceServer(grpcServer, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) AppName() string {
|
||||||
|
return app.AppService_ServiceDesc.ServiceName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) MethodPrefix() string {
|
||||||
|
return app.AppService_ServiceDesc.ServiceName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) AuthMethods() authz.MethodMapping {
|
||||||
|
return app.AppService_AuthMethods
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) RegisterGateway() server.RegisterGatewayFunc {
|
||||||
|
return app.RegisterAppServiceHandler
|
||||||
|
}
|
@@ -48,3 +48,26 @@ func QueryToPaginationPb(request query.SearchRequest, response query.SearchRespo
|
|||||||
TotalResult: response.Count,
|
TotalResult: response.Count,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TextMethodPbToQuery(method filter.TextFilterMethod) query.TextComparison {
|
||||||
|
switch method {
|
||||||
|
case filter.TextFilterMethod_TEXT_FILTER_METHOD_EQUALS:
|
||||||
|
return query.TextEquals
|
||||||
|
case filter.TextFilterMethod_TEXT_FILTER_METHOD_EQUALS_IGNORE_CASE:
|
||||||
|
return query.TextEqualsIgnoreCase
|
||||||
|
case filter.TextFilterMethod_TEXT_FILTER_METHOD_STARTS_WITH:
|
||||||
|
return query.TextStartsWith
|
||||||
|
case filter.TextFilterMethod_TEXT_FILTER_METHOD_STARTS_WITH_IGNORE_CASE:
|
||||||
|
return query.TextStartsWithIgnoreCase
|
||||||
|
case filter.TextFilterMethod_TEXT_FILTER_METHOD_CONTAINS:
|
||||||
|
return query.TextContains
|
||||||
|
case filter.TextFilterMethod_TEXT_FILTER_METHOD_CONTAINS_IGNORE_CASE:
|
||||||
|
return query.TextContainsIgnoreCase
|
||||||
|
case filter.TextFilterMethod_TEXT_FILTER_METHOD_ENDS_WITH:
|
||||||
|
return query.TextEndsWith
|
||||||
|
case filter.TextFilterMethod_TEXT_FILTER_METHOD_ENDS_WITH_IGNORE_CASE:
|
||||||
|
return query.TextEndsWithIgnoreCase
|
||||||
|
default:
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -29,7 +29,7 @@ func (s *Server) ListApps(ctx context.Context, req *mgmt_pb.ListAppsRequest) (*m
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
apps, err := s.query.SearchApps(ctx, queries, false)
|
apps, err := s.query.SearchApps(ctx, queries, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -125,7 +125,7 @@ func (s *Server) AddAPIApp(ctx context.Context, req *mgmt_pb.AddAPIAppRequest) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) UpdateApp(ctx context.Context, req *mgmt_pb.UpdateAppRequest) (*mgmt_pb.UpdateAppResponse, error) {
|
func (s *Server) UpdateApp(ctx context.Context, req *mgmt_pb.UpdateAppRequest) (*mgmt_pb.UpdateAppResponse, error) {
|
||||||
details, err := s.command.ChangeApplication(ctx, req.ProjectId, UpdateAppRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
|
details, err := s.command.UpdateApplicationName(ctx, req.ProjectId, UpdateAppRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -139,7 +139,7 @@ func (s *Server) UpdateOIDCAppConfig(ctx context.Context, req *mgmt_pb.UpdateOID
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
config, err := s.command.ChangeOIDCApplication(ctx, oidcApp, authz.GetCtxData(ctx).OrgID)
|
config, err := s.command.UpdateOIDCApplication(ctx, oidcApp, authz.GetCtxData(ctx).OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -157,7 +157,7 @@ func (s *Server) UpdateSAMLAppConfig(ctx context.Context, req *mgmt_pb.UpdateSAM
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
config, err := s.command.ChangeSAMLApplication(ctx, samlApp, authz.GetCtxData(ctx).OrgID)
|
config, err := s.command.UpdateSAMLApplication(ctx, samlApp, authz.GetCtxData(ctx).OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -171,7 +171,7 @@ func (s *Server) UpdateSAMLAppConfig(ctx context.Context, req *mgmt_pb.UpdateSAM
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) UpdateAPIAppConfig(ctx context.Context, req *mgmt_pb.UpdateAPIAppConfigRequest) (*mgmt_pb.UpdateAPIAppConfigResponse, error) {
|
func (s *Server) UpdateAPIAppConfig(ctx context.Context, req *mgmt_pb.UpdateAPIAppConfigRequest) (*mgmt_pb.UpdateAPIAppConfigResponse, error) {
|
||||||
config, err := s.command.ChangeAPIApplication(ctx, UpdateAPIAppConfigRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
|
config, err := s.command.UpdateAPIApplication(ctx, UpdateAPIAppConfigRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/authz"
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
authn_grpc "github.com/zitadel/zitadel/internal/api/grpc/authn"
|
authn_grpc "github.com/zitadel/zitadel/internal/api/grpc/authn"
|
||||||
"github.com/zitadel/zitadel/internal/api/grpc/object"
|
"github.com/zitadel/zitadel/internal/api/grpc/object"
|
||||||
@@ -46,24 +48,24 @@ func AddOIDCAppRequestToDomain(req *mgmt_pb.AddOIDCAppRequest) (*domain.OIDCApp,
|
|||||||
AggregateID: req.ProjectId,
|
AggregateID: req.ProjectId,
|
||||||
},
|
},
|
||||||
AppName: req.Name,
|
AppName: req.Name,
|
||||||
OIDCVersion: app_grpc.OIDCVersionToDomain(req.Version),
|
OIDCVersion: gu.Ptr(app_grpc.OIDCVersionToDomain(req.Version)),
|
||||||
RedirectUris: req.RedirectUris,
|
RedirectUris: req.RedirectUris,
|
||||||
ResponseTypes: app_grpc.OIDCResponseTypesToDomain(req.ResponseTypes),
|
ResponseTypes: app_grpc.OIDCResponseTypesToDomain(req.ResponseTypes),
|
||||||
GrantTypes: app_grpc.OIDCGrantTypesToDomain(req.GrantTypes),
|
GrantTypes: app_grpc.OIDCGrantTypesToDomain(req.GrantTypes),
|
||||||
ApplicationType: app_grpc.OIDCApplicationTypeToDomain(req.AppType),
|
ApplicationType: gu.Ptr(app_grpc.OIDCApplicationTypeToDomain(req.AppType)),
|
||||||
AuthMethodType: app_grpc.OIDCAuthMethodTypeToDomain(req.AuthMethodType),
|
AuthMethodType: gu.Ptr(app_grpc.OIDCAuthMethodTypeToDomain(req.AuthMethodType)),
|
||||||
PostLogoutRedirectUris: req.PostLogoutRedirectUris,
|
PostLogoutRedirectUris: req.PostLogoutRedirectUris,
|
||||||
DevMode: req.DevMode,
|
DevMode: gu.Ptr(req.GetDevMode()),
|
||||||
AccessTokenType: app_grpc.OIDCTokenTypeToDomain(req.AccessTokenType),
|
AccessTokenType: gu.Ptr(app_grpc.OIDCTokenTypeToDomain(req.AccessTokenType)),
|
||||||
AccessTokenRoleAssertion: req.AccessTokenRoleAssertion,
|
AccessTokenRoleAssertion: gu.Ptr(req.GetAccessTokenRoleAssertion()),
|
||||||
IDTokenRoleAssertion: req.IdTokenRoleAssertion,
|
IDTokenRoleAssertion: gu.Ptr(req.GetIdTokenRoleAssertion()),
|
||||||
IDTokenUserinfoAssertion: req.IdTokenUserinfoAssertion,
|
IDTokenUserinfoAssertion: gu.Ptr(req.GetIdTokenUserinfoAssertion()),
|
||||||
ClockSkew: req.ClockSkew.AsDuration(),
|
ClockSkew: gu.Ptr(req.GetClockSkew().AsDuration()),
|
||||||
AdditionalOrigins: req.AdditionalOrigins,
|
AdditionalOrigins: req.AdditionalOrigins,
|
||||||
SkipNativeAppSuccessPage: req.SkipNativeAppSuccessPage,
|
SkipNativeAppSuccessPage: gu.Ptr(req.GetSkipNativeAppSuccessPage()),
|
||||||
BackChannelLogoutURI: req.GetBackChannelLogoutUri(),
|
BackChannelLogoutURI: gu.Ptr(req.GetBackChannelLogoutUri()),
|
||||||
LoginVersion: loginVersion,
|
LoginVersion: gu.Ptr(loginVersion),
|
||||||
LoginBaseURI: loginBaseURI,
|
LoginBaseURI: gu.Ptr(loginBaseURI),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,9 +80,9 @@ func AddSAMLAppRequestToDomain(req *mgmt_pb.AddSAMLAppRequest) (*domain.SAMLApp,
|
|||||||
},
|
},
|
||||||
AppName: req.Name,
|
AppName: req.Name,
|
||||||
Metadata: req.GetMetadataXml(),
|
Metadata: req.GetMetadataXml(),
|
||||||
MetadataURL: req.GetMetadataUrl(),
|
MetadataURL: gu.Ptr(req.GetMetadataUrl()),
|
||||||
LoginVersion: loginVersion,
|
LoginVersion: gu.Ptr(loginVersion),
|
||||||
LoginBaseURI: loginBaseURI,
|
LoginBaseURI: gu.Ptr(loginBaseURI),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,20 +116,20 @@ func UpdateOIDCAppConfigRequestToDomain(app *mgmt_pb.UpdateOIDCAppConfigRequest)
|
|||||||
RedirectUris: app.RedirectUris,
|
RedirectUris: app.RedirectUris,
|
||||||
ResponseTypes: app_grpc.OIDCResponseTypesToDomain(app.ResponseTypes),
|
ResponseTypes: app_grpc.OIDCResponseTypesToDomain(app.ResponseTypes),
|
||||||
GrantTypes: app_grpc.OIDCGrantTypesToDomain(app.GrantTypes),
|
GrantTypes: app_grpc.OIDCGrantTypesToDomain(app.GrantTypes),
|
||||||
ApplicationType: app_grpc.OIDCApplicationTypeToDomain(app.AppType),
|
ApplicationType: gu.Ptr(app_grpc.OIDCApplicationTypeToDomain(app.AppType)),
|
||||||
AuthMethodType: app_grpc.OIDCAuthMethodTypeToDomain(app.AuthMethodType),
|
AuthMethodType: gu.Ptr(app_grpc.OIDCAuthMethodTypeToDomain(app.AuthMethodType)),
|
||||||
PostLogoutRedirectUris: app.PostLogoutRedirectUris,
|
PostLogoutRedirectUris: app.PostLogoutRedirectUris,
|
||||||
DevMode: app.DevMode,
|
DevMode: gu.Ptr(app.GetDevMode()),
|
||||||
AccessTokenType: app_grpc.OIDCTokenTypeToDomain(app.AccessTokenType),
|
AccessTokenType: gu.Ptr(app_grpc.OIDCTokenTypeToDomain(app.AccessTokenType)),
|
||||||
AccessTokenRoleAssertion: app.AccessTokenRoleAssertion,
|
AccessTokenRoleAssertion: gu.Ptr(app.GetAccessTokenRoleAssertion()),
|
||||||
IDTokenRoleAssertion: app.IdTokenRoleAssertion,
|
IDTokenRoleAssertion: gu.Ptr(app.GetIdTokenRoleAssertion()),
|
||||||
IDTokenUserinfoAssertion: app.IdTokenUserinfoAssertion,
|
IDTokenUserinfoAssertion: gu.Ptr(app.GetIdTokenUserinfoAssertion()),
|
||||||
ClockSkew: app.ClockSkew.AsDuration(),
|
ClockSkew: gu.Ptr(app.GetClockSkew().AsDuration()),
|
||||||
AdditionalOrigins: app.AdditionalOrigins,
|
AdditionalOrigins: app.AdditionalOrigins,
|
||||||
SkipNativeAppSuccessPage: app.SkipNativeAppSuccessPage,
|
SkipNativeAppSuccessPage: gu.Ptr(app.GetSkipNativeAppSuccessPage()),
|
||||||
BackChannelLogoutURI: app.BackChannelLogoutUri,
|
BackChannelLogoutURI: gu.Ptr(app.GetBackChannelLogoutUri()),
|
||||||
LoginVersion: loginVersion,
|
LoginVersion: gu.Ptr(loginVersion),
|
||||||
LoginBaseURI: loginBaseURI,
|
LoginBaseURI: gu.Ptr(loginBaseURI),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,9 +144,9 @@ func UpdateSAMLAppConfigRequestToDomain(app *mgmt_pb.UpdateSAMLAppConfigRequest)
|
|||||||
},
|
},
|
||||||
AppID: app.AppId,
|
AppID: app.AppId,
|
||||||
Metadata: app.GetMetadataXml(),
|
Metadata: app.GetMetadataXml(),
|
||||||
MetadataURL: app.GetMetadataUrl(),
|
MetadataURL: gu.Ptr(app.GetMetadataUrl()),
|
||||||
LoginVersion: loginVersion,
|
LoginVersion: gu.Ptr(loginVersion),
|
||||||
LoginBaseURI: loginBaseURI,
|
LoginBaseURI: gu.Ptr(loginBaseURI),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -32,7 +32,7 @@ func (v *View) ApplicationByProjecIDAndAppName(ctx context.Context, projectID, a
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
apps, err := v.Query.SearchApps(ctx, queries, false)
|
apps, err := v.Query.SearchApps(ctx, queries, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -85,3 +85,11 @@ func (c *Commands) checkPermissionDeleteProjectGrant(ctx context.Context, resour
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Commands) checkPermissionUpdateApplication(ctx context.Context, resourceOwner, appID string) error {
|
||||||
|
return c.newPermissionCheck(ctx, domain.PermissionProjectAppWrite, project.AggregateType)(resourceOwner, appID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) checkPermissionDeleteApp(ctx context.Context, resourceOwner, appID string) error {
|
||||||
|
return c.newPermissionCheck(ctx, domain.PermissionProjectAppDelete, project.AggregateType)(resourceOwner, appID)
|
||||||
|
}
|
||||||
|
@@ -15,7 +15,7 @@ type AddApp struct {
|
|||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) ChangeApplication(ctx context.Context, projectID string, appChange domain.Application, resourceOwner string) (*domain.ObjectDetails, error) {
|
func (c *Commands) UpdateApplicationName(ctx context.Context, projectID string, appChange domain.Application, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||||
if projectID == "" || appChange.GetAppID() == "" || appChange.GetApplicationName() == "" {
|
if projectID == "" || appChange.GetAppID() == "" || appChange.GetApplicationName() == "" {
|
||||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-4m9vS", "Errors.Project.App.Invalid")
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-4m9vS", "Errors.Project.App.Invalid")
|
||||||
}
|
}
|
||||||
@@ -30,6 +30,13 @@ func (c *Commands) ChangeApplication(ctx context.Context, projectID string, appC
|
|||||||
if existingApp.Name == appChange.GetApplicationName() {
|
if existingApp.Name == appChange.GetApplicationName() {
|
||||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-2m8vx", "Errors.NoChangesFound")
|
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-2m8vx", "Errors.NoChangesFound")
|
||||||
}
|
}
|
||||||
|
if err := c.eventstore.FilterToQueryReducer(ctx, existingApp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkPermissionUpdateApplication(ctx, existingApp.ResourceOwner, existingApp.AggregateID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
projectAgg := ProjectAggregateFromWriteModel(&existingApp.WriteModel)
|
projectAgg := ProjectAggregateFromWriteModel(&existingApp.WriteModel)
|
||||||
pushedEvents, err := c.eventstore.Push(
|
pushedEvents, err := c.eventstore.Push(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -59,6 +66,13 @@ func (c *Commands) DeactivateApplication(ctx context.Context, projectID, appID,
|
|||||||
if existingApp.State != domain.AppStateActive {
|
if existingApp.State != domain.AppStateActive {
|
||||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-dsh35", "Errors.Project.App.NotActive")
|
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-dsh35", "Errors.Project.App.NotActive")
|
||||||
}
|
}
|
||||||
|
if err := c.eventstore.FilterToQueryReducer(ctx, existingApp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkPermissionUpdateApplication(ctx, existingApp.ResourceOwner, existingApp.AggregateID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
projectAgg := ProjectAggregateFromWriteModel(&existingApp.WriteModel)
|
projectAgg := ProjectAggregateFromWriteModel(&existingApp.WriteModel)
|
||||||
pushedEvents, err := c.eventstore.Push(ctx, project.NewApplicationDeactivatedEvent(ctx, projectAgg, appID))
|
pushedEvents, err := c.eventstore.Push(ctx, project.NewApplicationDeactivatedEvent(ctx, projectAgg, appID))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -86,6 +100,11 @@ func (c *Commands) ReactivateApplication(ctx context.Context, projectID, appID,
|
|||||||
if existingApp.State != domain.AppStateInactive {
|
if existingApp.State != domain.AppStateInactive {
|
||||||
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-1n8cM", "Errors.Project.App.NotInactive")
|
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-1n8cM", "Errors.Project.App.NotInactive")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := c.checkPermissionUpdateApplication(ctx, existingApp.ResourceOwner, existingApp.AggregateID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
projectAgg := ProjectAggregateFromWriteModel(&existingApp.WriteModel)
|
projectAgg := ProjectAggregateFromWriteModel(&existingApp.WriteModel)
|
||||||
|
|
||||||
pushedEvents, err := c.eventstore.Push(ctx, project.NewApplicationReactivatedEvent(ctx, projectAgg, appID))
|
pushedEvents, err := c.eventstore.Push(ctx, project.NewApplicationReactivatedEvent(ctx, projectAgg, appID))
|
||||||
@@ -111,6 +130,13 @@ func (c *Commands) RemoveApplication(ctx context.Context, projectID, appID, reso
|
|||||||
if existingApp.State == domain.AppStateUnspecified || existingApp.State == domain.AppStateRemoved {
|
if existingApp.State == domain.AppStateUnspecified || existingApp.State == domain.AppStateRemoved {
|
||||||
return nil, zerrors.ThrowNotFound(nil, "COMMAND-0po9s", "Errors.Project.App.NotExisting")
|
return nil, zerrors.ThrowNotFound(nil, "COMMAND-0po9s", "Errors.Project.App.NotExisting")
|
||||||
}
|
}
|
||||||
|
if err := c.eventstore.FilterToQueryReducer(ctx, existingApp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkPermissionDeleteApp(ctx, existingApp.ResourceOwner, existingApp.AggregateID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
projectAgg := ProjectAggregateFromWriteModel(&existingApp.WriteModel)
|
projectAgg := ProjectAggregateFromWriteModel(&existingApp.WriteModel)
|
||||||
|
|
||||||
entityID := ""
|
entityID := ""
|
||||||
|
@@ -90,16 +90,24 @@ func (c *Commands) AddAPIApplication(ctx context.Context, apiApp *domain.APIApp,
|
|||||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-5m9E", "Errors.Project.App.Invalid")
|
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-5m9E", "Errors.Project.App.Invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := c.checkProjectExists(ctx, apiApp.AggregateID, resourceOwner); err != nil {
|
projectResOwner, err := c.checkProjectExists(ctx, apiApp.AggregateID, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if resourceOwner == "" {
|
||||||
|
resourceOwner = projectResOwner
|
||||||
|
}
|
||||||
|
|
||||||
if !apiApp.IsValid() {
|
if !apiApp.IsValid() {
|
||||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-Bff2g", "Errors.Project.App.Invalid")
|
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-Bff2g", "Errors.Project.App.Invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
appID, err := c.idGenerator.Next()
|
appID := apiApp.AppID
|
||||||
if err != nil {
|
if appID == "" {
|
||||||
return nil, err
|
appID, err = c.idGenerator.Next()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.addAPIApplicationWithID(ctx, apiApp, resourceOwner, appID)
|
return c.addAPIApplicationWithID(ctx, apiApp, resourceOwner, appID)
|
||||||
@@ -112,6 +120,13 @@ func (c *Commands) addAPIApplicationWithID(ctx context.Context, apiApp *domain.A
|
|||||||
apiApp.AppID = appID
|
apiApp.AppID = appID
|
||||||
|
|
||||||
addedApplication := NewAPIApplicationWriteModel(apiApp.AggregateID, resourceOwner)
|
addedApplication := NewAPIApplicationWriteModel(apiApp.AggregateID, resourceOwner)
|
||||||
|
if err := c.eventstore.FilterToQueryReducer(ctx, addedApplication); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkPermissionUpdateApplication(ctx, addedApplication.ResourceOwner, addedApplication.AggregateID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel)
|
projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel)
|
||||||
|
|
||||||
events := []eventstore.Command{
|
events := []eventstore.Command{
|
||||||
@@ -150,7 +165,7 @@ func (c *Commands) addAPIApplicationWithID(ctx context.Context, apiApp *domain.A
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) ChangeAPIApplication(ctx context.Context, apiApp *domain.APIApp, resourceOwner string) (*domain.APIApp, error) {
|
func (c *Commands) UpdateAPIApplication(ctx context.Context, apiApp *domain.APIApp, resourceOwner string) (*domain.APIApp, error) {
|
||||||
if apiApp.AppID == "" || apiApp.AggregateID == "" {
|
if apiApp.AppID == "" || apiApp.AggregateID == "" {
|
||||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-1m900", "Errors.Project.App.APIConfigInvalid")
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-1m900", "Errors.Project.App.APIConfigInvalid")
|
||||||
}
|
}
|
||||||
@@ -165,6 +180,13 @@ func (c *Commands) ChangeAPIApplication(ctx context.Context, apiApp *domain.APIA
|
|||||||
if !existingAPI.IsAPI() {
|
if !existingAPI.IsAPI() {
|
||||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Gnwt3", "Errors.Project.App.IsNotAPI")
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Gnwt3", "Errors.Project.App.IsNotAPI")
|
||||||
}
|
}
|
||||||
|
if err := c.eventstore.FilterToQueryReducer(ctx, existingAPI); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkPermissionUpdateApplication(ctx, existingAPI.ResourceOwner, existingAPI.AggregateID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
projectAgg := ProjectAggregateFromWriteModel(&existingAPI.WriteModel)
|
projectAgg := ProjectAggregateFromWriteModel(&existingAPI.WriteModel)
|
||||||
changedEvent, hasChanged, err := existingAPI.NewChangedEvent(
|
changedEvent, hasChanged, err := existingAPI.NewChangedEvent(
|
||||||
ctx,
|
ctx,
|
||||||
@@ -205,6 +227,11 @@ func (c *Commands) ChangeAPIApplicationSecret(ctx context.Context, projectID, ap
|
|||||||
if !existingAPI.IsAPI() {
|
if !existingAPI.IsAPI() {
|
||||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-aeH4", "Errors.Project.App.IsNotAPI")
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-aeH4", "Errors.Project.App.IsNotAPI")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := c.checkPermissionUpdateApplication(ctx, existingAPI.ResourceOwner, existingAPI.AggregateID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
encodedHash, plain, err := c.newHashedSecret(ctx, c.eventstore.Filter) //nolint:staticcheck
|
encodedHash, plain, err := c.newHashedSecret(ctx, c.eventstore.Filter) //nolint:staticcheck
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@@ -142,6 +142,7 @@ func TestAddAPIConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCommandSide_AddAPIApplication(t *testing.T) {
|
func TestCommandSide_AddAPIApplication(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||||
idGenerator id.Generator
|
idGenerator id.Generator
|
||||||
@@ -238,6 +239,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
|
|||||||
domain.PrivateLabelingSettingUnspecified),
|
domain.PrivateLabelingSettingUnspecified),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
project.NewApplicationAddedEvent(context.Background(),
|
project.NewApplicationAddedEvent(context.Background(),
|
||||||
&project.NewAggregate("project1", "org1").Aggregate,
|
&project.NewAggregate("project1", "org1").Aggregate,
|
||||||
@@ -292,6 +294,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
|
|||||||
domain.PrivateLabelingSettingUnspecified),
|
domain.PrivateLabelingSettingUnspecified),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
project.NewApplicationAddedEvent(context.Background(),
|
project.NewApplicationAddedEvent(context.Background(),
|
||||||
&project.NewAggregate("project1", "org1").Aggregate,
|
&project.NewAggregate("project1", "org1").Aggregate,
|
||||||
@@ -346,6 +349,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
|
|||||||
domain.PrivateLabelingSettingUnspecified),
|
domain.PrivateLabelingSettingUnspecified),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
project.NewApplicationAddedEvent(context.Background(),
|
project.NewApplicationAddedEvent(context.Background(),
|
||||||
&project.NewAggregate("project1", "org1").Aggregate,
|
&project.NewAggregate("project1", "org1").Aggregate,
|
||||||
@@ -390,6 +394,8 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
r := &Commands{
|
r := &Commands{
|
||||||
eventstore: tt.fields.eventstore(t),
|
eventstore: tt.fields.eventstore(t),
|
||||||
idGenerator: tt.fields.idGenerator,
|
idGenerator: tt.fields.idGenerator,
|
||||||
@@ -397,6 +403,7 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
|
|||||||
defaultSecretGenerators: &SecretGenerators{
|
defaultSecretGenerators: &SecretGenerators{
|
||||||
ClientSecret: emptyConfig,
|
ClientSecret: emptyConfig,
|
||||||
},
|
},
|
||||||
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
}
|
}
|
||||||
got, err := r.AddAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner)
|
got, err := r.AddAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner)
|
||||||
if tt.res.err == nil {
|
if tt.res.err == nil {
|
||||||
@@ -413,6 +420,8 @@ func TestCommandSide_AddAPIApplication(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCommandSide_ChangeAPIApplication(t *testing.T) {
|
func TestCommandSide_ChangeAPIApplication(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||||
}
|
}
|
||||||
@@ -516,6 +525,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
|
|||||||
domain.APIAuthMethodTypePrivateKeyJWT),
|
domain.APIAuthMethodTypePrivateKeyJWT),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
@@ -555,6 +565,7 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
|
|||||||
domain.APIAuthMethodTypeBasic),
|
domain.APIAuthMethodTypeBasic),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
newAPIAppChangedEvent(context.Background(),
|
newAPIAppChangedEvent(context.Background(),
|
||||||
"app1",
|
"app1",
|
||||||
@@ -593,14 +604,17 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
r := &Commands{
|
r := &Commands{
|
||||||
eventstore: tt.fields.eventstore(t),
|
eventstore: tt.fields.eventstore(t),
|
||||||
newHashedSecret: mockHashedSecret("secret"),
|
newHashedSecret: mockHashedSecret("secret"),
|
||||||
defaultSecretGenerators: &SecretGenerators{
|
defaultSecretGenerators: &SecretGenerators{
|
||||||
ClientSecret: emptyConfig,
|
ClientSecret: emptyConfig,
|
||||||
},
|
},
|
||||||
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
}
|
}
|
||||||
got, err := r.ChangeAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner)
|
got, err := r.UpdateAPIApplication(tt.args.ctx, tt.args.apiApp, tt.args.resourceOwner)
|
||||||
if tt.res.err == nil {
|
if tt.res.err == nil {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
@@ -615,6 +629,8 @@ func TestCommandSide_ChangeAPIApplication(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
|
func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore func(*testing.T) *eventstore.Eventstore
|
eventstore func(*testing.T) *eventstore.Eventstore
|
||||||
}
|
}
|
||||||
@@ -734,12 +750,15 @@ func TestCommandSide_ChangeAPIApplicationSecret(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
r := &Commands{
|
r := &Commands{
|
||||||
eventstore: tt.fields.eventstore(t),
|
eventstore: tt.fields.eventstore(t),
|
||||||
newHashedSecret: mockHashedSecret("secret"),
|
newHashedSecret: mockHashedSecret("secret"),
|
||||||
defaultSecretGenerators: &SecretGenerators{
|
defaultSecretGenerators: &SecretGenerators{
|
||||||
ClientSecret: emptyConfig,
|
ClientSecret: emptyConfig,
|
||||||
},
|
},
|
||||||
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
}
|
}
|
||||||
got, err := r.ChangeAPIApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
|
got, err := r.ChangeAPIApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
|
||||||
if tt.res.err == nil {
|
if tt.res.err == nil {
|
||||||
|
@@ -5,6 +5,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
|
||||||
http_util "github.com/zitadel/zitadel/internal/api/http"
|
http_util "github.com/zitadel/zitadel/internal/api/http"
|
||||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
@@ -120,6 +122,7 @@ func (c *Commands) AddOIDCAppCommand(app *addOIDCApp) preparation.Validation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Combine with AddOIDCApplication and addOIDCApplicationWithID
|
||||||
func (c *Commands) AddOIDCApplicationWithID(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner, appID string) (_ *domain.OIDCApp, err error) {
|
func (c *Commands) AddOIDCApplicationWithID(ctx context.Context, oidcApp *domain.OIDCApp, resourceOwner, appID string) (_ *domain.OIDCApp, err error) {
|
||||||
ctx, span := tracing.NewSpan(ctx)
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
defer func() { span.EndWithError(err) }()
|
defer func() { span.EndWithError(err) }()
|
||||||
@@ -142,9 +145,15 @@ func (c *Commands) AddOIDCApplication(ctx context.Context, oidcApp *domain.OIDCA
|
|||||||
if oidcApp == nil || oidcApp.AggregateID == "" {
|
if oidcApp == nil || oidcApp.AggregateID == "" {
|
||||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-34Fm0", "Errors.Project.App.Invalid")
|
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-34Fm0", "Errors.Project.App.Invalid")
|
||||||
}
|
}
|
||||||
if _, err := c.checkProjectExists(ctx, oidcApp.AggregateID, resourceOwner); err != nil {
|
|
||||||
|
projectResOwner, err := c.checkProjectExists(ctx, oidcApp.AggregateID, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if resourceOwner == "" {
|
||||||
|
resourceOwner = projectResOwner
|
||||||
|
}
|
||||||
|
|
||||||
if oidcApp.AppName == "" || !oidcApp.IsValid() {
|
if oidcApp.AppName == "" || !oidcApp.IsValid() {
|
||||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-1n8df", "Errors.Project.App.Invalid")
|
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-1n8df", "Errors.Project.App.Invalid")
|
||||||
}
|
}
|
||||||
@@ -162,6 +171,13 @@ func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain
|
|||||||
defer func() { span.EndWithError(err) }()
|
defer func() { span.EndWithError(err) }()
|
||||||
|
|
||||||
addedApplication := NewOIDCApplicationWriteModel(oidcApp.AggregateID, resourceOwner)
|
addedApplication := NewOIDCApplicationWriteModel(oidcApp.AggregateID, resourceOwner)
|
||||||
|
if err := c.eventstore.FilterToQueryReducer(ctx, addedApplication); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkPermissionUpdateApplication(ctx, addedApplication.ResourceOwner, addedApplication.AggregateID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel)
|
projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel)
|
||||||
|
|
||||||
oidcApp.AppID = appID
|
oidcApp.AppID = appID
|
||||||
@@ -183,27 +199,27 @@ func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain
|
|||||||
}
|
}
|
||||||
events = append(events, project_repo.NewOIDCConfigAddedEvent(ctx,
|
events = append(events, project_repo.NewOIDCConfigAddedEvent(ctx,
|
||||||
projectAgg,
|
projectAgg,
|
||||||
oidcApp.OIDCVersion,
|
gu.Value(oidcApp.OIDCVersion),
|
||||||
oidcApp.AppID,
|
oidcApp.AppID,
|
||||||
oidcApp.ClientID,
|
oidcApp.ClientID,
|
||||||
oidcApp.EncodedHash,
|
oidcApp.EncodedHash,
|
||||||
trimStringSliceWhiteSpaces(oidcApp.RedirectUris),
|
trimStringSliceWhiteSpaces(oidcApp.RedirectUris),
|
||||||
oidcApp.ResponseTypes,
|
oidcApp.ResponseTypes,
|
||||||
oidcApp.GrantTypes,
|
oidcApp.GrantTypes,
|
||||||
oidcApp.ApplicationType,
|
gu.Value(oidcApp.ApplicationType),
|
||||||
oidcApp.AuthMethodType,
|
gu.Value(oidcApp.AuthMethodType),
|
||||||
trimStringSliceWhiteSpaces(oidcApp.PostLogoutRedirectUris),
|
trimStringSliceWhiteSpaces(oidcApp.PostLogoutRedirectUris),
|
||||||
oidcApp.DevMode,
|
gu.Value(oidcApp.DevMode),
|
||||||
oidcApp.AccessTokenType,
|
gu.Value(oidcApp.AccessTokenType),
|
||||||
oidcApp.AccessTokenRoleAssertion,
|
gu.Value(oidcApp.AccessTokenRoleAssertion),
|
||||||
oidcApp.IDTokenRoleAssertion,
|
gu.Value(oidcApp.IDTokenRoleAssertion),
|
||||||
oidcApp.IDTokenUserinfoAssertion,
|
gu.Value(oidcApp.IDTokenUserinfoAssertion),
|
||||||
oidcApp.ClockSkew,
|
gu.Value(oidcApp.ClockSkew),
|
||||||
trimStringSliceWhiteSpaces(oidcApp.AdditionalOrigins),
|
trimStringSliceWhiteSpaces(oidcApp.AdditionalOrigins),
|
||||||
oidcApp.SkipNativeAppSuccessPage,
|
gu.Value(oidcApp.SkipNativeAppSuccessPage),
|
||||||
strings.TrimSpace(oidcApp.BackChannelLogoutURI),
|
strings.TrimSpace(gu.Value(oidcApp.BackChannelLogoutURI)),
|
||||||
oidcApp.LoginVersion,
|
gu.Value(oidcApp.LoginVersion),
|
||||||
strings.TrimSpace(oidcApp.LoginBaseURI),
|
strings.TrimSpace(gu.Value(oidcApp.LoginBaseURI)),
|
||||||
))
|
))
|
||||||
|
|
||||||
addedApplication.AppID = oidcApp.AppID
|
addedApplication.AppID = oidcApp.AppID
|
||||||
@@ -226,7 +242,7 @@ func (c *Commands) addOIDCApplicationWithID(ctx context.Context, oidcApp *domain
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) ChangeOIDCApplication(ctx context.Context, oidc *domain.OIDCApp, resourceOwner string) (*domain.OIDCApp, error) {
|
func (c *Commands) UpdateOIDCApplication(ctx context.Context, oidc *domain.OIDCApp, resourceOwner string) (*domain.OIDCApp, error) {
|
||||||
if !oidc.IsValid() || oidc.AppID == "" || oidc.AggregateID == "" {
|
if !oidc.IsValid() || oidc.AppID == "" || oidc.AggregateID == "" {
|
||||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-5m9fs", "Errors.Project.App.OIDCConfigInvalid")
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-5m9fs", "Errors.Project.App.OIDCConfigInvalid")
|
||||||
}
|
}
|
||||||
@@ -241,7 +257,23 @@ func (c *Commands) ChangeOIDCApplication(ctx context.Context, oidc *domain.OIDCA
|
|||||||
if !existingOIDC.IsOIDC() {
|
if !existingOIDC.IsOIDC() {
|
||||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-GBr34", "Errors.Project.App.IsNotOIDC")
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-GBr34", "Errors.Project.App.IsNotOIDC")
|
||||||
}
|
}
|
||||||
|
if err := c.eventstore.FilterToQueryReducer(ctx, existingOIDC); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkPermissionUpdateApplication(ctx, existingOIDC.ResourceOwner, existingOIDC.AggregateID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
projectAgg := ProjectAggregateFromWriteModel(&existingOIDC.WriteModel)
|
projectAgg := ProjectAggregateFromWriteModel(&existingOIDC.WriteModel)
|
||||||
|
var backChannelLogout, loginBaseURI *string
|
||||||
|
if oidc.BackChannelLogoutURI != nil {
|
||||||
|
backChannelLogout = gu.Ptr(strings.TrimSpace(*oidc.BackChannelLogoutURI))
|
||||||
|
}
|
||||||
|
|
||||||
|
if oidc.LoginBaseURI != nil {
|
||||||
|
loginBaseURI = gu.Ptr(strings.TrimSpace(*oidc.LoginBaseURI))
|
||||||
|
}
|
||||||
|
|
||||||
changedEvent, hasChanged, err := existingOIDC.NewChangedEvent(
|
changedEvent, hasChanged, err := existingOIDC.NewChangedEvent(
|
||||||
ctx,
|
ctx,
|
||||||
projectAgg,
|
projectAgg,
|
||||||
@@ -261,9 +293,9 @@ func (c *Commands) ChangeOIDCApplication(ctx context.Context, oidc *domain.OIDCA
|
|||||||
oidc.ClockSkew,
|
oidc.ClockSkew,
|
||||||
trimStringSliceWhiteSpaces(oidc.AdditionalOrigins),
|
trimStringSliceWhiteSpaces(oidc.AdditionalOrigins),
|
||||||
oidc.SkipNativeAppSuccessPage,
|
oidc.SkipNativeAppSuccessPage,
|
||||||
strings.TrimSpace(oidc.BackChannelLogoutURI),
|
backChannelLogout,
|
||||||
oidc.LoginVersion,
|
oidc.LoginVersion,
|
||||||
strings.TrimSpace(oidc.LoginBaseURI),
|
loginBaseURI,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -301,6 +333,11 @@ func (c *Commands) ChangeOIDCApplicationSecret(ctx context.Context, projectID, a
|
|||||||
if !existingOIDC.IsOIDC() {
|
if !existingOIDC.IsOIDC() {
|
||||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Ghrh3", "Errors.Project.App.IsNotOIDC")
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Ghrh3", "Errors.Project.App.IsNotOIDC")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := c.checkPermissionUpdateApplication(ctx, existingOIDC.ResourceOwner, existingOIDC.AggregateID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
encodedHash, plain, err := c.newHashedSecret(ctx, c.eventstore.Filter) //nolint:staticcheck
|
encodedHash, plain, err := c.newHashedSecret(ctx, c.eventstore.Filter) //nolint:staticcheck
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@@ -258,77 +258,77 @@ func (wm *OIDCApplicationWriteModel) NewChangedEvent(
|
|||||||
postLogoutRedirectURIs []string,
|
postLogoutRedirectURIs []string,
|
||||||
responseTypes []domain.OIDCResponseType,
|
responseTypes []domain.OIDCResponseType,
|
||||||
grantTypes []domain.OIDCGrantType,
|
grantTypes []domain.OIDCGrantType,
|
||||||
appType domain.OIDCApplicationType,
|
appType *domain.OIDCApplicationType,
|
||||||
authMethodType domain.OIDCAuthMethodType,
|
authMethodType *domain.OIDCAuthMethodType,
|
||||||
oidcVersion domain.OIDCVersion,
|
oidcVersion *domain.OIDCVersion,
|
||||||
accessTokenType domain.OIDCTokenType,
|
accessTokenType *domain.OIDCTokenType,
|
||||||
devMode,
|
devMode,
|
||||||
accessTokenRoleAssertion,
|
accessTokenRoleAssertion,
|
||||||
idTokenRoleAssertion,
|
idTokenRoleAssertion,
|
||||||
idTokenUserinfoAssertion bool,
|
idTokenUserinfoAssertion *bool,
|
||||||
clockSkew time.Duration,
|
clockSkew *time.Duration,
|
||||||
additionalOrigins []string,
|
additionalOrigins []string,
|
||||||
skipNativeAppSuccessPage bool,
|
skipNativeAppSuccessPage *bool,
|
||||||
backChannelLogoutURI string,
|
backChannelLogoutURI *string,
|
||||||
loginVersion domain.LoginVersion,
|
loginVersion *domain.LoginVersion,
|
||||||
loginBaseURI string,
|
loginBaseURI *string,
|
||||||
) (*project.OIDCConfigChangedEvent, bool, error) {
|
) (*project.OIDCConfigChangedEvent, bool, error) {
|
||||||
changes := make([]project.OIDCConfigChanges, 0)
|
changes := make([]project.OIDCConfigChanges, 0)
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if !slices.Equal(wm.RedirectUris, redirectURIS) {
|
if redirectURIS != nil && !slices.Equal(wm.RedirectUris, redirectURIS) {
|
||||||
changes = append(changes, project.ChangeRedirectURIs(redirectURIS))
|
changes = append(changes, project.ChangeRedirectURIs(redirectURIS))
|
||||||
}
|
}
|
||||||
if !slices.Equal(wm.ResponseTypes, responseTypes) {
|
if responseTypes != nil && !slices.Equal(wm.ResponseTypes, responseTypes) {
|
||||||
changes = append(changes, project.ChangeResponseTypes(responseTypes))
|
changes = append(changes, project.ChangeResponseTypes(responseTypes))
|
||||||
}
|
}
|
||||||
if !slices.Equal(wm.GrantTypes, grantTypes) {
|
if grantTypes != nil && !slices.Equal(wm.GrantTypes, grantTypes) {
|
||||||
changes = append(changes, project.ChangeGrantTypes(grantTypes))
|
changes = append(changes, project.ChangeGrantTypes(grantTypes))
|
||||||
}
|
}
|
||||||
if wm.ApplicationType != appType {
|
if appType != nil && wm.ApplicationType != *appType {
|
||||||
changes = append(changes, project.ChangeApplicationType(appType))
|
changes = append(changes, project.ChangeApplicationType(*appType))
|
||||||
}
|
}
|
||||||
if wm.AuthMethodType != authMethodType {
|
if authMethodType != nil && wm.AuthMethodType != *authMethodType {
|
||||||
changes = append(changes, project.ChangeAuthMethodType(authMethodType))
|
changes = append(changes, project.ChangeAuthMethodType(*authMethodType))
|
||||||
}
|
}
|
||||||
if !slices.Equal(wm.PostLogoutRedirectUris, postLogoutRedirectURIs) {
|
if postLogoutRedirectURIs != nil && !slices.Equal(wm.PostLogoutRedirectUris, postLogoutRedirectURIs) {
|
||||||
changes = append(changes, project.ChangePostLogoutRedirectURIs(postLogoutRedirectURIs))
|
changes = append(changes, project.ChangePostLogoutRedirectURIs(postLogoutRedirectURIs))
|
||||||
}
|
}
|
||||||
if wm.OIDCVersion != oidcVersion {
|
if oidcVersion != nil && wm.OIDCVersion != *oidcVersion {
|
||||||
changes = append(changes, project.ChangeVersion(oidcVersion))
|
changes = append(changes, project.ChangeVersion(*oidcVersion))
|
||||||
}
|
}
|
||||||
if wm.DevMode != devMode {
|
if devMode != nil && wm.DevMode != *devMode {
|
||||||
changes = append(changes, project.ChangeDevMode(devMode))
|
changes = append(changes, project.ChangeDevMode(*devMode))
|
||||||
}
|
}
|
||||||
if wm.AccessTokenType != accessTokenType {
|
if accessTokenType != nil && wm.AccessTokenType != *accessTokenType {
|
||||||
changes = append(changes, project.ChangeAccessTokenType(accessTokenType))
|
changes = append(changes, project.ChangeAccessTokenType(*accessTokenType))
|
||||||
}
|
}
|
||||||
if wm.AccessTokenRoleAssertion != accessTokenRoleAssertion {
|
if accessTokenRoleAssertion != nil && wm.AccessTokenRoleAssertion != *accessTokenRoleAssertion {
|
||||||
changes = append(changes, project.ChangeAccessTokenRoleAssertion(accessTokenRoleAssertion))
|
changes = append(changes, project.ChangeAccessTokenRoleAssertion(*accessTokenRoleAssertion))
|
||||||
}
|
}
|
||||||
if wm.IDTokenRoleAssertion != idTokenRoleAssertion {
|
if idTokenRoleAssertion != nil && wm.IDTokenRoleAssertion != *idTokenRoleAssertion {
|
||||||
changes = append(changes, project.ChangeIDTokenRoleAssertion(idTokenRoleAssertion))
|
changes = append(changes, project.ChangeIDTokenRoleAssertion(*idTokenRoleAssertion))
|
||||||
}
|
}
|
||||||
if wm.IDTokenUserinfoAssertion != idTokenUserinfoAssertion {
|
if idTokenUserinfoAssertion != nil && wm.IDTokenUserinfoAssertion != *idTokenUserinfoAssertion {
|
||||||
changes = append(changes, project.ChangeIDTokenUserinfoAssertion(idTokenUserinfoAssertion))
|
changes = append(changes, project.ChangeIDTokenUserinfoAssertion(*idTokenUserinfoAssertion))
|
||||||
}
|
}
|
||||||
if wm.ClockSkew != clockSkew {
|
if clockSkew != nil && wm.ClockSkew != *clockSkew {
|
||||||
changes = append(changes, project.ChangeClockSkew(clockSkew))
|
changes = append(changes, project.ChangeClockSkew(*clockSkew))
|
||||||
}
|
}
|
||||||
if !slices.Equal(wm.AdditionalOrigins, additionalOrigins) {
|
if additionalOrigins != nil && !slices.Equal(wm.AdditionalOrigins, additionalOrigins) {
|
||||||
changes = append(changes, project.ChangeAdditionalOrigins(additionalOrigins))
|
changes = append(changes, project.ChangeAdditionalOrigins(additionalOrigins))
|
||||||
}
|
}
|
||||||
if wm.SkipNativeAppSuccessPage != skipNativeAppSuccessPage {
|
if skipNativeAppSuccessPage != nil && wm.SkipNativeAppSuccessPage != *skipNativeAppSuccessPage {
|
||||||
changes = append(changes, project.ChangeSkipNativeAppSuccessPage(skipNativeAppSuccessPage))
|
changes = append(changes, project.ChangeSkipNativeAppSuccessPage(*skipNativeAppSuccessPage))
|
||||||
}
|
}
|
||||||
if wm.BackChannelLogoutURI != backChannelLogoutURI {
|
if backChannelLogoutURI != nil && wm.BackChannelLogoutURI != *backChannelLogoutURI {
|
||||||
changes = append(changes, project.ChangeBackChannelLogoutURI(backChannelLogoutURI))
|
changes = append(changes, project.ChangeBackChannelLogoutURI(*backChannelLogoutURI))
|
||||||
}
|
}
|
||||||
if wm.LoginVersion != loginVersion {
|
if loginVersion != nil && wm.LoginVersion != *loginVersion {
|
||||||
changes = append(changes, project.ChangeOIDCLoginVersion(loginVersion))
|
changes = append(changes, project.ChangeOIDCLoginVersion(*loginVersion))
|
||||||
}
|
}
|
||||||
if wm.LoginBaseURI != loginBaseURI {
|
if loginBaseURI != nil && wm.LoginBaseURI != *loginBaseURI {
|
||||||
changes = append(changes, project.ChangeOIDCLoginBaseURI(loginBaseURI))
|
changes = append(changes, project.ChangeOIDCLoginBaseURI(*loginBaseURI))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(changes) == 0 {
|
if len(changes) == 0 {
|
||||||
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/authz"
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
@@ -401,6 +402,8 @@ func TestAddOIDCApp(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||||
idGenerator id.Generator
|
idGenerator id.Generator
|
||||||
@@ -497,6 +500,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
|||||||
domain.PrivateLabelingSettingUnspecified),
|
domain.PrivateLabelingSettingUnspecified),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
project.NewApplicationAddedEvent(context.Background(),
|
project.NewApplicationAddedEvent(context.Background(),
|
||||||
&project.NewAggregate("project1", "org1").Aggregate,
|
&project.NewAggregate("project1", "org1").Aggregate,
|
||||||
@@ -538,24 +542,24 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
|||||||
AggregateID: "project1",
|
AggregateID: "project1",
|
||||||
},
|
},
|
||||||
AppName: "app",
|
AppName: "app",
|
||||||
AuthMethodType: domain.OIDCAuthMethodTypePost,
|
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypePost),
|
||||||
OIDCVersion: domain.OIDCVersionV1,
|
OIDCVersion: gu.Ptr(domain.OIDCVersionV1),
|
||||||
RedirectUris: []string{" https://test.ch "},
|
RedirectUris: []string{" https://test.ch "},
|
||||||
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||||
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||||
ApplicationType: domain.OIDCApplicationTypeWeb,
|
ApplicationType: gu.Ptr(domain.OIDCApplicationTypeWeb),
|
||||||
PostLogoutRedirectUris: []string{" https://test.ch/logout "},
|
PostLogoutRedirectUris: []string{" https://test.ch/logout "},
|
||||||
DevMode: true,
|
DevMode: gu.Ptr(true),
|
||||||
AccessTokenType: domain.OIDCTokenTypeBearer,
|
AccessTokenType: gu.Ptr(domain.OIDCTokenTypeBearer),
|
||||||
AccessTokenRoleAssertion: true,
|
AccessTokenRoleAssertion: gu.Ptr(true),
|
||||||
IDTokenRoleAssertion: true,
|
IDTokenRoleAssertion: gu.Ptr(true),
|
||||||
IDTokenUserinfoAssertion: true,
|
IDTokenUserinfoAssertion: gu.Ptr(true),
|
||||||
ClockSkew: time.Second * 1,
|
ClockSkew: gu.Ptr(time.Second * 1),
|
||||||
AdditionalOrigins: []string{" https://sub.test.ch "},
|
AdditionalOrigins: []string{" https://sub.test.ch "},
|
||||||
SkipNativeAppSuccessPage: true,
|
SkipNativeAppSuccessPage: gu.Ptr(true),
|
||||||
BackChannelLogoutURI: " https://test.ch/backchannel ",
|
BackChannelLogoutURI: gu.Ptr(" https://test.ch/backchannel "),
|
||||||
LoginVersion: domain.LoginVersion2,
|
LoginVersion: gu.Ptr(domain.LoginVersion2),
|
||||||
LoginBaseURI: " https://login.test.ch ",
|
LoginBaseURI: gu.Ptr(" https://login.test.ch "),
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@@ -569,24 +573,24 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
|||||||
AppName: "app",
|
AppName: "app",
|
||||||
ClientID: "client1",
|
ClientID: "client1",
|
||||||
ClientSecretString: "secret",
|
ClientSecretString: "secret",
|
||||||
AuthMethodType: domain.OIDCAuthMethodTypePost,
|
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypePost),
|
||||||
OIDCVersion: domain.OIDCVersionV1,
|
OIDCVersion: gu.Ptr(domain.OIDCVersionV1),
|
||||||
RedirectUris: []string{"https://test.ch"},
|
RedirectUris: []string{"https://test.ch"},
|
||||||
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||||
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||||
ApplicationType: domain.OIDCApplicationTypeWeb,
|
ApplicationType: gu.Ptr(domain.OIDCApplicationTypeWeb),
|
||||||
PostLogoutRedirectUris: []string{"https://test.ch/logout"},
|
PostLogoutRedirectUris: []string{"https://test.ch/logout"},
|
||||||
DevMode: true,
|
DevMode: gu.Ptr(true),
|
||||||
AccessTokenType: domain.OIDCTokenTypeBearer,
|
AccessTokenType: gu.Ptr(domain.OIDCTokenTypeBearer),
|
||||||
AccessTokenRoleAssertion: true,
|
AccessTokenRoleAssertion: gu.Ptr(true),
|
||||||
IDTokenRoleAssertion: true,
|
IDTokenRoleAssertion: gu.Ptr(true),
|
||||||
IDTokenUserinfoAssertion: true,
|
IDTokenUserinfoAssertion: gu.Ptr(true),
|
||||||
ClockSkew: time.Second * 1,
|
ClockSkew: gu.Ptr(time.Second * 1),
|
||||||
AdditionalOrigins: []string{"https://sub.test.ch"},
|
AdditionalOrigins: []string{"https://sub.test.ch"},
|
||||||
SkipNativeAppSuccessPage: true,
|
SkipNativeAppSuccessPage: gu.Ptr(true),
|
||||||
BackChannelLogoutURI: "https://test.ch/backchannel",
|
BackChannelLogoutURI: gu.Ptr("https://test.ch/backchannel"),
|
||||||
LoginVersion: domain.LoginVersion2,
|
LoginVersion: gu.Ptr(domain.LoginVersion2),
|
||||||
LoginBaseURI: "https://login.test.ch",
|
LoginBaseURI: gu.Ptr("https://login.test.ch"),
|
||||||
State: domain.AppStateActive,
|
State: domain.AppStateActive,
|
||||||
Compliance: &domain.Compliance{},
|
Compliance: &domain.Compliance{},
|
||||||
},
|
},
|
||||||
@@ -604,6 +608,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
|||||||
domain.PrivateLabelingSettingUnspecified),
|
domain.PrivateLabelingSettingUnspecified),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
project.NewApplicationAddedEvent(context.Background(),
|
project.NewApplicationAddedEvent(context.Background(),
|
||||||
&project.NewAggregate("project1", "org1").Aggregate,
|
&project.NewAggregate("project1", "org1").Aggregate,
|
||||||
@@ -645,24 +650,24 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
|||||||
AggregateID: "project1",
|
AggregateID: "project1",
|
||||||
},
|
},
|
||||||
AppName: "app",
|
AppName: "app",
|
||||||
AuthMethodType: domain.OIDCAuthMethodTypePost,
|
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypePost),
|
||||||
OIDCVersion: domain.OIDCVersionV1,
|
OIDCVersion: gu.Ptr(domain.OIDCVersionV1),
|
||||||
RedirectUris: []string{"https://test.ch"},
|
RedirectUris: []string{"https://test.ch"},
|
||||||
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||||
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||||
ApplicationType: domain.OIDCApplicationTypeWeb,
|
ApplicationType: gu.Ptr(domain.OIDCApplicationTypeWeb),
|
||||||
PostLogoutRedirectUris: []string{"https://test.ch/logout"},
|
PostLogoutRedirectUris: []string{"https://test.ch/logout"},
|
||||||
DevMode: true,
|
DevMode: gu.Ptr(true),
|
||||||
AccessTokenType: domain.OIDCTokenTypeBearer,
|
AccessTokenType: gu.Ptr(domain.OIDCTokenTypeBearer),
|
||||||
AccessTokenRoleAssertion: true,
|
AccessTokenRoleAssertion: gu.Ptr(true),
|
||||||
IDTokenRoleAssertion: true,
|
IDTokenRoleAssertion: gu.Ptr(true),
|
||||||
IDTokenUserinfoAssertion: true,
|
IDTokenUserinfoAssertion: gu.Ptr(true),
|
||||||
ClockSkew: time.Second * 1,
|
ClockSkew: gu.Ptr(time.Second * 1),
|
||||||
AdditionalOrigins: []string{"https://sub.test.ch"},
|
AdditionalOrigins: []string{"https://sub.test.ch"},
|
||||||
SkipNativeAppSuccessPage: true,
|
SkipNativeAppSuccessPage: gu.Ptr(true),
|
||||||
BackChannelLogoutURI: "https://test.ch/backchannel",
|
BackChannelLogoutURI: gu.Ptr("https://test.ch/backchannel"),
|
||||||
LoginVersion: domain.LoginVersion2,
|
LoginVersion: gu.Ptr(domain.LoginVersion2),
|
||||||
LoginBaseURI: "https://login.test.ch",
|
LoginBaseURI: gu.Ptr("https://login.test.ch"),
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@@ -676,24 +681,24 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
|||||||
AppName: "app",
|
AppName: "app",
|
||||||
ClientID: "client1",
|
ClientID: "client1",
|
||||||
ClientSecretString: "secret",
|
ClientSecretString: "secret",
|
||||||
AuthMethodType: domain.OIDCAuthMethodTypePost,
|
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypePost),
|
||||||
OIDCVersion: domain.OIDCVersionV1,
|
OIDCVersion: gu.Ptr(domain.OIDCVersionV1),
|
||||||
RedirectUris: []string{"https://test.ch"},
|
RedirectUris: []string{"https://test.ch"},
|
||||||
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||||
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||||
ApplicationType: domain.OIDCApplicationTypeWeb,
|
ApplicationType: gu.Ptr(domain.OIDCApplicationTypeWeb),
|
||||||
PostLogoutRedirectUris: []string{"https://test.ch/logout"},
|
PostLogoutRedirectUris: []string{"https://test.ch/logout"},
|
||||||
DevMode: true,
|
DevMode: gu.Ptr(true),
|
||||||
AccessTokenType: domain.OIDCTokenTypeBearer,
|
AccessTokenType: gu.Ptr(domain.OIDCTokenTypeBearer),
|
||||||
AccessTokenRoleAssertion: true,
|
AccessTokenRoleAssertion: gu.Ptr(true),
|
||||||
IDTokenRoleAssertion: true,
|
IDTokenRoleAssertion: gu.Ptr(true),
|
||||||
IDTokenUserinfoAssertion: true,
|
IDTokenUserinfoAssertion: gu.Ptr(true),
|
||||||
ClockSkew: time.Second * 1,
|
ClockSkew: gu.Ptr(time.Second * 1),
|
||||||
AdditionalOrigins: []string{"https://sub.test.ch"},
|
AdditionalOrigins: []string{"https://sub.test.ch"},
|
||||||
SkipNativeAppSuccessPage: true,
|
SkipNativeAppSuccessPage: gu.Ptr(true),
|
||||||
BackChannelLogoutURI: "https://test.ch/backchannel",
|
BackChannelLogoutURI: gu.Ptr("https://test.ch/backchannel"),
|
||||||
LoginVersion: domain.LoginVersion2,
|
LoginVersion: gu.Ptr(domain.LoginVersion2),
|
||||||
LoginBaseURI: "https://login.test.ch",
|
LoginBaseURI: gu.Ptr("https://login.test.ch"),
|
||||||
State: domain.AppStateActive,
|
State: domain.AppStateActive,
|
||||||
Compliance: &domain.Compliance{},
|
Compliance: &domain.Compliance{},
|
||||||
},
|
},
|
||||||
@@ -702,6 +707,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
c := &Commands{
|
c := &Commands{
|
||||||
eventstore: tt.fields.eventstore(t),
|
eventstore: tt.fields.eventstore(t),
|
||||||
idGenerator: tt.fields.idGenerator,
|
idGenerator: tt.fields.idGenerator,
|
||||||
@@ -709,6 +715,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
|||||||
defaultSecretGenerators: &SecretGenerators{
|
defaultSecretGenerators: &SecretGenerators{
|
||||||
ClientSecret: emptyConfig,
|
ClientSecret: emptyConfig,
|
||||||
},
|
},
|
||||||
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
}
|
}
|
||||||
c.setMilestonesCompletedForTest("instanceID")
|
c.setMilestonesCompletedForTest("instanceID")
|
||||||
got, err := c.AddOIDCApplication(tt.args.ctx, tt.args.oidcApp, tt.args.resourceOwner)
|
got, err := c.AddOIDCApplication(tt.args.ctx, tt.args.oidcApp, tt.args.resourceOwner)
|
||||||
@@ -726,6 +733,7 @@ func TestCommandSide_AddOIDCApplication(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore func(*testing.T) *eventstore.Eventstore
|
eventstore func(*testing.T) *eventstore.Eventstore
|
||||||
}
|
}
|
||||||
@@ -775,7 +783,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
AggregateID: "project1",
|
AggregateID: "project1",
|
||||||
},
|
},
|
||||||
AppID: "",
|
AppID: "",
|
||||||
AuthMethodType: domain.OIDCAuthMethodTypePost,
|
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypePost),
|
||||||
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||||
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||||
},
|
},
|
||||||
@@ -797,7 +805,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
AggregateID: "",
|
AggregateID: "",
|
||||||
},
|
},
|
||||||
AppID: "appid",
|
AppID: "appid",
|
||||||
AuthMethodType: domain.OIDCAuthMethodTypePost,
|
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypePost),
|
||||||
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||||
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||||
},
|
},
|
||||||
@@ -821,7 +829,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
AggregateID: "project1",
|
AggregateID: "project1",
|
||||||
},
|
},
|
||||||
AppID: "app1",
|
AppID: "app1",
|
||||||
AuthMethodType: domain.OIDCAuthMethodTypePost,
|
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypePost),
|
||||||
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||||
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||||
},
|
},
|
||||||
@@ -870,6 +878,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
@@ -880,24 +889,24 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
},
|
},
|
||||||
AppID: "app1",
|
AppID: "app1",
|
||||||
AppName: "app",
|
AppName: "app",
|
||||||
AuthMethodType: domain.OIDCAuthMethodTypePost,
|
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypePost),
|
||||||
OIDCVersion: domain.OIDCVersionV1,
|
OIDCVersion: gu.Ptr(domain.OIDCVersionV1),
|
||||||
RedirectUris: []string{"https://test.ch"},
|
RedirectUris: []string{"https://test.ch"},
|
||||||
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||||
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||||
ApplicationType: domain.OIDCApplicationTypeWeb,
|
ApplicationType: gu.Ptr(domain.OIDCApplicationTypeWeb),
|
||||||
PostLogoutRedirectUris: []string{"https://test.ch/logout"},
|
PostLogoutRedirectUris: []string{"https://test.ch/logout"},
|
||||||
DevMode: true,
|
DevMode: gu.Ptr(true),
|
||||||
AccessTokenType: domain.OIDCTokenTypeBearer,
|
AccessTokenType: gu.Ptr(domain.OIDCTokenTypeBearer),
|
||||||
AccessTokenRoleAssertion: true,
|
AccessTokenRoleAssertion: gu.Ptr(true),
|
||||||
IDTokenRoleAssertion: true,
|
IDTokenRoleAssertion: gu.Ptr(true),
|
||||||
IDTokenUserinfoAssertion: true,
|
IDTokenUserinfoAssertion: gu.Ptr(true),
|
||||||
ClockSkew: time.Second * 1,
|
ClockSkew: gu.Ptr(time.Second * 1),
|
||||||
AdditionalOrigins: []string{"https://sub.test.ch"},
|
AdditionalOrigins: []string{"https://sub.test.ch"},
|
||||||
SkipNativeAppSuccessPage: true,
|
SkipNativeAppSuccessPage: gu.Ptr(true),
|
||||||
BackChannelLogoutURI: "https://test.ch/backchannel",
|
BackChannelLogoutURI: gu.Ptr("https://test.ch/backchannel"),
|
||||||
LoginVersion: domain.LoginVersion2,
|
LoginVersion: gu.Ptr(domain.LoginVersion2),
|
||||||
LoginBaseURI: "https://login.test.ch",
|
LoginBaseURI: gu.Ptr("https://login.test.ch"),
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@@ -944,6 +953,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
@@ -954,24 +964,24 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
},
|
},
|
||||||
AppID: "app1",
|
AppID: "app1",
|
||||||
AppName: "app",
|
AppName: "app",
|
||||||
AuthMethodType: domain.OIDCAuthMethodTypePost,
|
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypePost),
|
||||||
OIDCVersion: domain.OIDCVersionV1,
|
OIDCVersion: gu.Ptr(domain.OIDCVersionV1),
|
||||||
RedirectUris: []string{"https://test.ch "},
|
RedirectUris: []string{"https://test.ch "},
|
||||||
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||||
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||||
ApplicationType: domain.OIDCApplicationTypeWeb,
|
ApplicationType: gu.Ptr(domain.OIDCApplicationTypeWeb),
|
||||||
PostLogoutRedirectUris: []string{" https://test.ch/logout"},
|
PostLogoutRedirectUris: []string{" https://test.ch/logout"},
|
||||||
DevMode: true,
|
DevMode: gu.Ptr(true),
|
||||||
AccessTokenType: domain.OIDCTokenTypeBearer,
|
AccessTokenType: gu.Ptr(domain.OIDCTokenTypeBearer),
|
||||||
AccessTokenRoleAssertion: true,
|
AccessTokenRoleAssertion: gu.Ptr(true),
|
||||||
IDTokenRoleAssertion: true,
|
IDTokenRoleAssertion: gu.Ptr(true),
|
||||||
IDTokenUserinfoAssertion: true,
|
IDTokenUserinfoAssertion: gu.Ptr(true),
|
||||||
ClockSkew: time.Second * 1,
|
ClockSkew: gu.Ptr(time.Second * 1),
|
||||||
AdditionalOrigins: []string{" https://sub.test.ch "},
|
AdditionalOrigins: []string{" https://sub.test.ch "},
|
||||||
SkipNativeAppSuccessPage: true,
|
SkipNativeAppSuccessPage: gu.Ptr(true),
|
||||||
BackChannelLogoutURI: " https://test.ch/backchannel ",
|
BackChannelLogoutURI: gu.Ptr(" https://test.ch/backchannel "),
|
||||||
LoginVersion: domain.LoginVersion2,
|
LoginVersion: gu.Ptr(domain.LoginVersion2),
|
||||||
LoginBaseURI: " https://login.test.ch ",
|
LoginBaseURI: gu.Ptr(" https://login.test.ch "),
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@@ -980,7 +990,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "change oidc app, ok",
|
name: "partial change oidc app, ok",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: expectEventstore(
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
@@ -1018,6 +1028,7 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
newOIDCAppChangedEvent(context.Background(),
|
newOIDCAppChangedEvent(context.Background(),
|
||||||
"app1",
|
"app1",
|
||||||
@@ -1032,26 +1043,11 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
ObjectRoot: models.ObjectRoot{
|
ObjectRoot: models.ObjectRoot{
|
||||||
AggregateID: "project1",
|
AggregateID: "project1",
|
||||||
},
|
},
|
||||||
AppID: "app1",
|
AppID: "app1",
|
||||||
AppName: "app",
|
AppName: "app",
|
||||||
AuthMethodType: domain.OIDCAuthMethodTypePost,
|
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypeBasic),
|
||||||
OIDCVersion: domain.OIDCVersionV1,
|
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||||
RedirectUris: []string{" https://test-change.ch "},
|
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||||
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
|
||||||
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
|
||||||
ApplicationType: domain.OIDCApplicationTypeWeb,
|
|
||||||
PostLogoutRedirectUris: []string{" https://test-change.ch/logout "},
|
|
||||||
DevMode: true,
|
|
||||||
AccessTokenType: domain.OIDCTokenTypeJWT,
|
|
||||||
AccessTokenRoleAssertion: false,
|
|
||||||
IDTokenRoleAssertion: false,
|
|
||||||
IDTokenUserinfoAssertion: false,
|
|
||||||
ClockSkew: time.Second * 2,
|
|
||||||
AdditionalOrigins: []string{"https://sub.test.ch"},
|
|
||||||
SkipNativeAppSuccessPage: true,
|
|
||||||
BackChannelLogoutURI: "https://test.ch/backchannel",
|
|
||||||
LoginVersion: domain.LoginVersion2,
|
|
||||||
LoginBaseURI: "https://login.test.ch",
|
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@@ -1064,24 +1060,24 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
AppID: "app1",
|
AppID: "app1",
|
||||||
ClientID: "client1@project",
|
ClientID: "client1@project",
|
||||||
AppName: "app",
|
AppName: "app",
|
||||||
AuthMethodType: domain.OIDCAuthMethodTypePost,
|
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypeBasic),
|
||||||
OIDCVersion: domain.OIDCVersionV1,
|
OIDCVersion: gu.Ptr(domain.OIDCVersionV1),
|
||||||
RedirectUris: []string{"https://test-change.ch"},
|
RedirectUris: []string{"https://test.ch"},
|
||||||
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||||
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||||
ApplicationType: domain.OIDCApplicationTypeWeb,
|
ApplicationType: gu.Ptr(domain.OIDCApplicationTypeWeb),
|
||||||
PostLogoutRedirectUris: []string{"https://test-change.ch/logout"},
|
PostLogoutRedirectUris: []string{"https://test.ch/logout"},
|
||||||
DevMode: true,
|
DevMode: gu.Ptr(false),
|
||||||
AccessTokenType: domain.OIDCTokenTypeJWT,
|
AccessTokenType: gu.Ptr(domain.OIDCTokenTypeBearer),
|
||||||
AccessTokenRoleAssertion: false,
|
AccessTokenRoleAssertion: gu.Ptr(true),
|
||||||
IDTokenRoleAssertion: false,
|
IDTokenRoleAssertion: gu.Ptr(true),
|
||||||
IDTokenUserinfoAssertion: false,
|
IDTokenUserinfoAssertion: gu.Ptr(true),
|
||||||
ClockSkew: time.Second * 2,
|
ClockSkew: gu.Ptr(time.Second * 1),
|
||||||
AdditionalOrigins: []string{"https://sub.test.ch"},
|
AdditionalOrigins: []string{"https://sub.test.ch"},
|
||||||
SkipNativeAppSuccessPage: true,
|
SkipNativeAppSuccessPage: gu.Ptr(true),
|
||||||
BackChannelLogoutURI: "https://test.ch/backchannel",
|
BackChannelLogoutURI: gu.Ptr("https://test.ch/backchannel"),
|
||||||
LoginVersion: domain.LoginVersion2,
|
LoginVersion: gu.Ptr(domain.LoginVersion1),
|
||||||
LoginBaseURI: "https://login.test.ch",
|
LoginBaseURI: gu.Ptr(""),
|
||||||
Compliance: &domain.Compliance{},
|
Compliance: &domain.Compliance{},
|
||||||
State: domain.AppStateActive,
|
State: domain.AppStateActive,
|
||||||
},
|
},
|
||||||
@@ -1090,10 +1086,12 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// t.Parallel()
|
||||||
r := &Commands{
|
r := &Commands{
|
||||||
eventstore: tt.fields.eventstore(t),
|
eventstore: tt.fields.eventstore(t),
|
||||||
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
}
|
}
|
||||||
got, err := r.ChangeOIDCApplication(tt.args.ctx, tt.args.oidcApp, tt.args.resourceOwner)
|
got, err := r.UpdateOIDCApplication(tt.args.ctx, tt.args.oidcApp, tt.args.resourceOwner)
|
||||||
if tt.res.err == nil {
|
if tt.res.err == nil {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
@@ -1108,6 +1106,8 @@ func TestCommandSide_ChangeOIDCApplication(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
|
func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore func(*testing.T) *eventstore.Eventstore
|
eventstore func(*testing.T) *eventstore.Eventstore
|
||||||
}
|
}
|
||||||
@@ -1237,36 +1237,40 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
|
|||||||
AppName: "app",
|
AppName: "app",
|
||||||
ClientID: "client1@project",
|
ClientID: "client1@project",
|
||||||
ClientSecretString: "secret",
|
ClientSecretString: "secret",
|
||||||
AuthMethodType: domain.OIDCAuthMethodTypePost,
|
AuthMethodType: gu.Ptr(domain.OIDCAuthMethodTypePost),
|
||||||
OIDCVersion: domain.OIDCVersionV1,
|
OIDCVersion: gu.Ptr(domain.OIDCVersionV1),
|
||||||
RedirectUris: []string{"https://test.ch"},
|
RedirectUris: []string{"https://test.ch"},
|
||||||
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
ResponseTypes: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||||
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
GrantTypes: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||||
ApplicationType: domain.OIDCApplicationTypeWeb,
|
ApplicationType: gu.Ptr(domain.OIDCApplicationTypeWeb),
|
||||||
PostLogoutRedirectUris: []string{"https://test.ch/logout"},
|
PostLogoutRedirectUris: []string{"https://test.ch/logout"},
|
||||||
DevMode: true,
|
DevMode: gu.Ptr(true),
|
||||||
AccessTokenType: domain.OIDCTokenTypeBearer,
|
AccessTokenType: gu.Ptr(domain.OIDCTokenTypeBearer),
|
||||||
AccessTokenRoleAssertion: true,
|
AccessTokenRoleAssertion: gu.Ptr(true),
|
||||||
IDTokenRoleAssertion: true,
|
IDTokenRoleAssertion: gu.Ptr(true),
|
||||||
IDTokenUserinfoAssertion: true,
|
IDTokenUserinfoAssertion: gu.Ptr(true),
|
||||||
ClockSkew: time.Second * 1,
|
ClockSkew: gu.Ptr(time.Second * 1),
|
||||||
AdditionalOrigins: []string{"https://sub.test.ch"},
|
AdditionalOrigins: []string{"https://sub.test.ch"},
|
||||||
SkipNativeAppSuccessPage: false,
|
SkipNativeAppSuccessPage: gu.Ptr(false),
|
||||||
BackChannelLogoutURI: "",
|
BackChannelLogoutURI: gu.Ptr(""),
|
||||||
LoginVersion: domain.LoginVersionUnspecified,
|
LoginVersion: gu.Ptr(domain.LoginVersionUnspecified),
|
||||||
|
LoginBaseURI: gu.Ptr(""),
|
||||||
State: domain.AppStateActive,
|
State: domain.AppStateActive,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(*testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
r := &Commands{
|
r := &Commands{
|
||||||
eventstore: tt.fields.eventstore(t),
|
eventstore: tt.fields.eventstore(t),
|
||||||
newHashedSecret: mockHashedSecret("secret"),
|
newHashedSecret: mockHashedSecret("secret"),
|
||||||
defaultSecretGenerators: &SecretGenerators{
|
defaultSecretGenerators: &SecretGenerators{
|
||||||
ClientSecret: emptyConfig,
|
ClientSecret: emptyConfig,
|
||||||
},
|
},
|
||||||
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
}
|
}
|
||||||
got, err := r.ChangeOIDCApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
|
got, err := r.ChangeOIDCApplicationSecret(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
|
||||||
if tt.res.err == nil {
|
if tt.res.err == nil {
|
||||||
@@ -1284,16 +1288,7 @@ func TestCommandSide_ChangeOIDCApplicationSecret(t *testing.T) {
|
|||||||
|
|
||||||
func newOIDCAppChangedEvent(ctx context.Context, appID, projectID, resourceOwner string) *project.OIDCConfigChangedEvent {
|
func newOIDCAppChangedEvent(ctx context.Context, appID, projectID, resourceOwner string) *project.OIDCConfigChangedEvent {
|
||||||
changes := []project.OIDCConfigChanges{
|
changes := []project.OIDCConfigChanges{
|
||||||
project.ChangeRedirectURIs([]string{"https://test-change.ch"}),
|
project.ChangeAuthMethodType(domain.OIDCAuthMethodTypeBasic),
|
||||||
project.ChangePostLogoutRedirectURIs([]string{"https://test-change.ch/logout"}),
|
|
||||||
project.ChangeDevMode(true),
|
|
||||||
project.ChangeAccessTokenType(domain.OIDCTokenTypeJWT),
|
|
||||||
project.ChangeAccessTokenRoleAssertion(false),
|
|
||||||
project.ChangeIDTokenRoleAssertion(false),
|
|
||||||
project.ChangeIDTokenUserinfoAssertion(false),
|
|
||||||
project.ChangeClockSkew(time.Second * 2),
|
|
||||||
project.ChangeOIDCLoginVersion(domain.LoginVersion2),
|
|
||||||
project.ChangeOIDCLoginBaseURI("https://login.test.ch"),
|
|
||||||
}
|
}
|
||||||
event, _ := project.NewOIDCConfigChangedEvent(ctx,
|
event, _ := project.NewOIDCConfigChangedEvent(ctx,
|
||||||
&project.NewAggregate(projectID, resourceOwner).Aggregate,
|
&project.NewAggregate(projectID, resourceOwner).Aggregate,
|
||||||
|
@@ -3,6 +3,7 @@ package command
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
"github.com/zitadel/saml/pkg/provider/xml"
|
"github.com/zitadel/saml/pkg/provider/xml"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
@@ -16,10 +17,22 @@ func (c *Commands) AddSAMLApplication(ctx context.Context, application *domain.S
|
|||||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-35Fn0", "Errors.Project.App.Invalid")
|
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-35Fn0", "Errors.Project.App.Invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := c.checkProjectExists(ctx, application.AggregateID, resourceOwner); err != nil {
|
projectResOwner, err := c.checkProjectExists(ctx, application.AggregateID, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if resourceOwner == "" {
|
||||||
|
resourceOwner = projectResOwner
|
||||||
|
}
|
||||||
|
|
||||||
addedApplication := NewSAMLApplicationWriteModel(application.AggregateID, resourceOwner)
|
addedApplication := NewSAMLApplicationWriteModel(application.AggregateID, resourceOwner)
|
||||||
|
if err := c.eventstore.FilterToQueryReducer(ctx, addedApplication); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := c.checkPermissionUpdateApplication(ctx, addedApplication.ResourceOwner, addedApplication.AggregateID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel)
|
projectAgg := ProjectAggregateFromWriteModel(&addedApplication.WriteModel)
|
||||||
events, err := c.addSAMLApplication(ctx, projectAgg, application)
|
events, err := c.addSAMLApplication(ctx, projectAgg, application)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -49,12 +62,8 @@ func (c *Commands) addSAMLApplication(ctx context.Context, projectAgg *eventstor
|
|||||||
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-1n9df", "Errors.Project.App.Invalid")
|
return nil, zerrors.ThrowInvalidArgument(nil, "PROJECT-1n9df", "Errors.Project.App.Invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
if samlApp.Metadata == nil && samlApp.MetadataURL == "" {
|
if samlApp.MetadataURL != nil && *samlApp.MetadataURL != "" {
|
||||||
return nil, zerrors.ThrowInvalidArgument(nil, "SAML-podix9", "Errors.Project.App.SAMLMetadataMissing")
|
data, err := xml.ReadMetadataFromURL(c.httpClient, *samlApp.MetadataURL)
|
||||||
}
|
|
||||||
|
|
||||||
if samlApp.MetadataURL != "" {
|
|
||||||
data, err := xml.ReadMetadataFromURL(c.httpClient, samlApp.MetadataURL)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, zerrors.ThrowInvalidArgument(err, "SAML-wmqlo1", "Errors.Project.App.SAMLMetadataMissing")
|
return nil, zerrors.ThrowInvalidArgument(err, "SAML-wmqlo1", "Errors.Project.App.SAMLMetadataMissing")
|
||||||
}
|
}
|
||||||
@@ -78,14 +87,14 @@ func (c *Commands) addSAMLApplication(ctx context.Context, projectAgg *eventstor
|
|||||||
samlApp.AppID,
|
samlApp.AppID,
|
||||||
string(entity.EntityID),
|
string(entity.EntityID),
|
||||||
samlApp.Metadata,
|
samlApp.Metadata,
|
||||||
samlApp.MetadataURL,
|
gu.Value(samlApp.MetadataURL),
|
||||||
samlApp.LoginVersion,
|
gu.Value(samlApp.LoginVersion),
|
||||||
samlApp.LoginBaseURI,
|
gu.Value(samlApp.LoginBaseURI),
|
||||||
),
|
),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) ChangeSAMLApplication(ctx context.Context, samlApp *domain.SAMLApp, resourceOwner string) (*domain.SAMLApp, error) {
|
func (c *Commands) UpdateSAMLApplication(ctx context.Context, samlApp *domain.SAMLApp, resourceOwner string) (*domain.SAMLApp, error) {
|
||||||
if !samlApp.IsValid() || samlApp.AppID == "" || samlApp.AggregateID == "" {
|
if !samlApp.IsValid() || samlApp.AppID == "" || samlApp.AggregateID == "" {
|
||||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-5n9fs", "Errors.Project.App.SAMLConfigInvalid")
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-5n9fs", "Errors.Project.App.SAMLConfigInvalid")
|
||||||
}
|
}
|
||||||
@@ -100,10 +109,15 @@ func (c *Commands) ChangeSAMLApplication(ctx context.Context, samlApp *domain.SA
|
|||||||
if !existingSAML.IsSAML() {
|
if !existingSAML.IsSAML() {
|
||||||
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-GBr35", "Errors.Project.App.IsNotSAML")
|
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-GBr35", "Errors.Project.App.IsNotSAML")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := c.checkPermissionUpdateApplication(ctx, existingSAML.ResourceOwner, existingSAML.AggregateID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
projectAgg := ProjectAggregateFromWriteModel(&existingSAML.WriteModel)
|
projectAgg := ProjectAggregateFromWriteModel(&existingSAML.WriteModel)
|
||||||
|
|
||||||
if samlApp.MetadataURL != "" {
|
if samlApp.MetadataURL != nil && *samlApp.MetadataURL != "" {
|
||||||
data, err := xml.ReadMetadataFromURL(c.httpClient, samlApp.MetadataURL)
|
data, err := xml.ReadMetadataFromURL(c.httpClient, *samlApp.MetadataURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, zerrors.ThrowInvalidArgument(err, "SAML-J3kg3", "Errors.Project.App.SAMLMetadataMissing")
|
return nil, zerrors.ThrowInvalidArgument(err, "SAML-J3kg3", "Errors.Project.App.SAMLMetadataMissing")
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@ package command
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"reflect"
|
"slices"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
@@ -170,26 +170,26 @@ func (wm *SAMLApplicationWriteModel) NewChangedEvent(
|
|||||||
appID string,
|
appID string,
|
||||||
entityID string,
|
entityID string,
|
||||||
metadata []byte,
|
metadata []byte,
|
||||||
metadataURL string,
|
metadataURL *string,
|
||||||
loginVersion domain.LoginVersion,
|
loginVersion *domain.LoginVersion,
|
||||||
loginBaseURI string,
|
loginBaseURI *string,
|
||||||
) (*project.SAMLConfigChangedEvent, bool, error) {
|
) (*project.SAMLConfigChangedEvent, bool, error) {
|
||||||
changes := make([]project.SAMLConfigChanges, 0)
|
changes := make([]project.SAMLConfigChanges, 0)
|
||||||
var err error
|
var err error
|
||||||
if !reflect.DeepEqual(wm.Metadata, metadata) {
|
if metadata != nil && !slices.Equal(wm.Metadata, metadata) {
|
||||||
changes = append(changes, project.ChangeMetadata(metadata))
|
changes = append(changes, project.ChangeMetadata(metadata))
|
||||||
}
|
}
|
||||||
if wm.MetadataURL != metadataURL {
|
if metadataURL != nil && wm.MetadataURL != *metadataURL {
|
||||||
changes = append(changes, project.ChangeMetadataURL(metadataURL))
|
changes = append(changes, project.ChangeMetadataURL(*metadataURL))
|
||||||
}
|
}
|
||||||
if wm.EntityID != entityID {
|
if wm.EntityID != entityID {
|
||||||
changes = append(changes, project.ChangeEntityID(entityID))
|
changes = append(changes, project.ChangeEntityID(entityID))
|
||||||
}
|
}
|
||||||
if wm.LoginVersion != loginVersion {
|
if loginVersion != nil && wm.LoginVersion != *loginVersion {
|
||||||
changes = append(changes, project.ChangeSAMLLoginVersion(loginVersion))
|
changes = append(changes, project.ChangeSAMLLoginVersion(*loginVersion))
|
||||||
}
|
}
|
||||||
if wm.LoginBaseURI != loginBaseURI {
|
if loginBaseURI != nil && wm.LoginBaseURI != *loginBaseURI {
|
||||||
changes = append(changes, project.ChangeSAMLLoginBaseURI(loginBaseURI))
|
changes = append(changes, project.ChangeSAMLLoginBaseURI(*loginBaseURI))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(changes) == 0 {
|
if len(changes) == 0 {
|
||||||
|
@@ -7,6 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/authz"
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
@@ -49,6 +50,8 @@ var testMetadataChangedEntityID = []byte(`<?xml version="1.0"?>
|
|||||||
`)
|
`)
|
||||||
|
|
||||||
func TestCommandSide_AddSAMLApplication(t *testing.T) {
|
func TestCommandSide_AddSAMLApplication(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||||
idGenerator id.Generator
|
idGenerator id.Generator
|
||||||
@@ -117,6 +120,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
|
|||||||
domain.PrivateLabelingSettingUnspecified),
|
domain.PrivateLabelingSettingUnspecified),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
@@ -134,6 +138,37 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
|
|||||||
err: zerrors.IsErrorInvalidArgument,
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "empty metas, invalid argument error",
|
||||||
|
fields: fields{
|
||||||
|
eventstore: expectEventstore(
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
project.NewProjectAddedEvent(context.Background(),
|
||||||
|
&project.NewAggregate("project1", "org1").Aggregate,
|
||||||
|
"project", true, true, true,
|
||||||
|
domain.PrivateLabelingSettingUnspecified),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
expectFilter(),
|
||||||
|
),
|
||||||
|
idGenerator: id_mock.NewIDGeneratorExpectIDs(t),
|
||||||
|
},
|
||||||
|
args: args{
|
||||||
|
ctx: authz.WithInstanceID(context.Background(), "instanceID"),
|
||||||
|
samlApp: &domain.SAMLApp{
|
||||||
|
ObjectRoot: models.ObjectRoot{
|
||||||
|
AggregateID: "project1",
|
||||||
|
},
|
||||||
|
AppName: "app",
|
||||||
|
EntityID: "https://test.com/saml/metadata",
|
||||||
|
},
|
||||||
|
resourceOwner: "org1",
|
||||||
|
},
|
||||||
|
res: res{
|
||||||
|
err: zerrors.IsErrorInvalidArgument,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "create saml app, metadata not parsable",
|
name: "create saml app, metadata not parsable",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
@@ -146,6 +181,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
|
|||||||
domain.PrivateLabelingSettingUnspecified),
|
domain.PrivateLabelingSettingUnspecified),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(),
|
||||||
),
|
),
|
||||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t),
|
idGenerator: id_mock.NewIDGeneratorExpectIDs(t),
|
||||||
},
|
},
|
||||||
@@ -158,7 +194,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
|
|||||||
AppName: "app",
|
AppName: "app",
|
||||||
EntityID: "https://test.com/saml/metadata",
|
EntityID: "https://test.com/saml/metadata",
|
||||||
Metadata: []byte("test metadata"),
|
Metadata: []byte("test metadata"),
|
||||||
MetadataURL: "",
|
MetadataURL: gu.Ptr(""),
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@@ -178,6 +214,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
|
|||||||
domain.PrivateLabelingSettingUnspecified),
|
domain.PrivateLabelingSettingUnspecified),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
project.NewApplicationAddedEvent(context.Background(),
|
project.NewApplicationAddedEvent(context.Background(),
|
||||||
&project.NewAggregate("project1", "org1").Aggregate,
|
&project.NewAggregate("project1", "org1").Aggregate,
|
||||||
@@ -206,7 +243,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
|
|||||||
AppName: "app",
|
AppName: "app",
|
||||||
EntityID: "https://test.com/saml/metadata",
|
EntityID: "https://test.com/saml/metadata",
|
||||||
Metadata: testMetadata,
|
Metadata: testMetadata,
|
||||||
MetadataURL: "",
|
MetadataURL: gu.Ptr(""),
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@@ -216,12 +253,14 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
|
|||||||
AggregateID: "project1",
|
AggregateID: "project1",
|
||||||
ResourceOwner: "org1",
|
ResourceOwner: "org1",
|
||||||
},
|
},
|
||||||
AppID: "app1",
|
AppID: "app1",
|
||||||
AppName: "app",
|
AppName: "app",
|
||||||
EntityID: "https://test.com/saml/metadata",
|
EntityID: "https://test.com/saml/metadata",
|
||||||
Metadata: testMetadata,
|
Metadata: testMetadata,
|
||||||
MetadataURL: "",
|
MetadataURL: gu.Ptr(""),
|
||||||
State: domain.AppStateActive,
|
State: domain.AppStateActive,
|
||||||
|
LoginVersion: gu.Ptr(domain.LoginVersionUnspecified),
|
||||||
|
LoginBaseURI: gu.Ptr(""),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -237,6 +276,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
|
|||||||
domain.PrivateLabelingSettingUnspecified),
|
domain.PrivateLabelingSettingUnspecified),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
project.NewApplicationAddedEvent(context.Background(),
|
project.NewApplicationAddedEvent(context.Background(),
|
||||||
&project.NewAggregate("project1", "org1").Aggregate,
|
&project.NewAggregate("project1", "org1").Aggregate,
|
||||||
@@ -265,9 +305,9 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
|
|||||||
AppName: "app",
|
AppName: "app",
|
||||||
EntityID: "https://test.com/saml/metadata",
|
EntityID: "https://test.com/saml/metadata",
|
||||||
Metadata: testMetadata,
|
Metadata: testMetadata,
|
||||||
MetadataURL: "",
|
MetadataURL: gu.Ptr(""),
|
||||||
LoginVersion: domain.LoginVersion2,
|
LoginVersion: gu.Ptr(domain.LoginVersion2),
|
||||||
LoginBaseURI: "https://test.com/login",
|
LoginBaseURI: gu.Ptr("https://test.com/login"),
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@@ -281,10 +321,10 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
|
|||||||
AppName: "app",
|
AppName: "app",
|
||||||
EntityID: "https://test.com/saml/metadata",
|
EntityID: "https://test.com/saml/metadata",
|
||||||
Metadata: testMetadata,
|
Metadata: testMetadata,
|
||||||
MetadataURL: "",
|
MetadataURL: gu.Ptr(""),
|
||||||
State: domain.AppStateActive,
|
State: domain.AppStateActive,
|
||||||
LoginVersion: domain.LoginVersion2,
|
LoginVersion: gu.Ptr(domain.LoginVersion2),
|
||||||
LoginBaseURI: "https://test.com/login",
|
LoginBaseURI: gu.Ptr("https://test.com/login"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -300,6 +340,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
|
|||||||
domain.PrivateLabelingSettingUnspecified),
|
domain.PrivateLabelingSettingUnspecified),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
project.NewApplicationAddedEvent(context.Background(),
|
project.NewApplicationAddedEvent(context.Background(),
|
||||||
&project.NewAggregate("project1", "org1").Aggregate,
|
&project.NewAggregate("project1", "org1").Aggregate,
|
||||||
@@ -329,7 +370,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
|
|||||||
AppName: "app",
|
AppName: "app",
|
||||||
EntityID: "https://test.com/saml/metadata",
|
EntityID: "https://test.com/saml/metadata",
|
||||||
Metadata: nil,
|
Metadata: nil,
|
||||||
MetadataURL: "http://localhost:8080/saml/metadata",
|
MetadataURL: gu.Ptr("http://localhost:8080/saml/metadata"),
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@@ -339,12 +380,14 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
|
|||||||
AggregateID: "project1",
|
AggregateID: "project1",
|
||||||
ResourceOwner: "org1",
|
ResourceOwner: "org1",
|
||||||
},
|
},
|
||||||
AppID: "app1",
|
AppID: "app1",
|
||||||
AppName: "app",
|
AppName: "app",
|
||||||
EntityID: "https://test.com/saml/metadata",
|
EntityID: "https://test.com/saml/metadata",
|
||||||
Metadata: testMetadata,
|
Metadata: testMetadata,
|
||||||
MetadataURL: "http://localhost:8080/saml/metadata",
|
MetadataURL: gu.Ptr("http://localhost:8080/saml/metadata"),
|
||||||
State: domain.AppStateActive,
|
State: domain.AppStateActive,
|
||||||
|
LoginVersion: gu.Ptr(domain.LoginVersionUnspecified),
|
||||||
|
LoginBaseURI: gu.Ptr(""),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -360,6 +403,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
|
|||||||
domain.PrivateLabelingSettingUnspecified),
|
domain.PrivateLabelingSettingUnspecified),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(),
|
||||||
),
|
),
|
||||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t),
|
idGenerator: id_mock.NewIDGeneratorExpectIDs(t),
|
||||||
httpClient: newTestClient(http.StatusNotFound, nil),
|
httpClient: newTestClient(http.StatusNotFound, nil),
|
||||||
@@ -373,7 +417,7 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
|
|||||||
AppName: "app",
|
AppName: "app",
|
||||||
EntityID: "https://test.com/saml/metadata",
|
EntityID: "https://test.com/saml/metadata",
|
||||||
Metadata: nil,
|
Metadata: nil,
|
||||||
MetadataURL: "http://localhost:8080/saml/metadata",
|
MetadataURL: gu.Ptr("http://localhost:8080/saml/metadata"),
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@@ -385,10 +429,13 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
c := &Commands{
|
c := &Commands{
|
||||||
eventstore: tt.fields.eventstore(t),
|
eventstore: tt.fields.eventstore(t),
|
||||||
idGenerator: tt.fields.idGenerator,
|
idGenerator: tt.fields.idGenerator,
|
||||||
httpClient: tt.fields.httpClient,
|
httpClient: tt.fields.httpClient,
|
||||||
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
}
|
}
|
||||||
c.setMilestonesCompletedForTest("instanceID")
|
c.setMilestonesCompletedForTest("instanceID")
|
||||||
got, err := c.AddSAMLApplication(tt.args.ctx, tt.args.samlApp, tt.args.resourceOwner)
|
got, err := c.AddSAMLApplication(tt.args.ctx, tt.args.samlApp, tt.args.resourceOwner)
|
||||||
@@ -406,6 +453,8 @@ func TestCommandSide_AddSAMLApplication(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
|
func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore func(t *testing.T) *eventstore.Eventstore
|
eventstore func(t *testing.T) *eventstore.Eventstore
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
@@ -544,7 +593,7 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
|
|||||||
AppID: "app1",
|
AppID: "app1",
|
||||||
EntityID: "https://test.com/saml/metadata",
|
EntityID: "https://test.com/saml/metadata",
|
||||||
Metadata: nil,
|
Metadata: nil,
|
||||||
MetadataURL: "http://localhost:8080/saml/metadata",
|
MetadataURL: gu.Ptr("http://localhost:8080/saml/metadata"),
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@@ -590,7 +639,7 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
|
|||||||
AppID: "app1",
|
AppID: "app1",
|
||||||
EntityID: "https://test.com/saml/metadata",
|
EntityID: "https://test.com/saml/metadata",
|
||||||
Metadata: testMetadata,
|
Metadata: testMetadata,
|
||||||
MetadataURL: "",
|
MetadataURL: gu.Ptr(""),
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@@ -646,7 +695,7 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
|
|||||||
AppName: "app",
|
AppName: "app",
|
||||||
EntityID: "https://test2.com/saml/metadata",
|
EntityID: "https://test2.com/saml/metadata",
|
||||||
Metadata: nil,
|
Metadata: nil,
|
||||||
MetadataURL: "http://localhost:8080/saml/metadata",
|
MetadataURL: gu.Ptr("http://localhost:8080/saml/metadata"),
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@@ -656,17 +705,19 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
|
|||||||
AggregateID: "project1",
|
AggregateID: "project1",
|
||||||
ResourceOwner: "org1",
|
ResourceOwner: "org1",
|
||||||
},
|
},
|
||||||
AppID: "app1",
|
AppID: "app1",
|
||||||
AppName: "app",
|
AppName: "app",
|
||||||
EntityID: "https://test2.com/saml/metadata",
|
EntityID: "https://test2.com/saml/metadata",
|
||||||
Metadata: testMetadataChangedEntityID,
|
Metadata: testMetadataChangedEntityID,
|
||||||
MetadataURL: "http://localhost:8080/saml/metadata",
|
MetadataURL: gu.Ptr("http://localhost:8080/saml/metadata"),
|
||||||
State: domain.AppStateActive,
|
State: domain.AppStateActive,
|
||||||
|
LoginVersion: gu.Ptr(domain.LoginVersionUnspecified),
|
||||||
|
LoginBaseURI: gu.Ptr(""),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "change saml app, ok, metadata",
|
name: "partial change saml app, ok, metadata",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: expectEventstore(
|
eventstore: expectEventstore(
|
||||||
expectFilter(
|
expectFilter(
|
||||||
@@ -713,7 +764,7 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
|
|||||||
AppName: "app",
|
AppName: "app",
|
||||||
EntityID: "https://test2.com/saml/metadata",
|
EntityID: "https://test2.com/saml/metadata",
|
||||||
Metadata: testMetadataChangedEntityID,
|
Metadata: testMetadataChangedEntityID,
|
||||||
MetadataURL: "",
|
MetadataURL: gu.Ptr(""),
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@@ -723,15 +774,18 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
|
|||||||
AggregateID: "project1",
|
AggregateID: "project1",
|
||||||
ResourceOwner: "org1",
|
ResourceOwner: "org1",
|
||||||
},
|
},
|
||||||
AppID: "app1",
|
AppID: "app1",
|
||||||
AppName: "app",
|
AppName: "app",
|
||||||
EntityID: "https://test2.com/saml/metadata",
|
EntityID: "https://test2.com/saml/metadata",
|
||||||
Metadata: testMetadataChangedEntityID,
|
Metadata: testMetadataChangedEntityID,
|
||||||
MetadataURL: "",
|
MetadataURL: gu.Ptr(""),
|
||||||
State: domain.AppStateActive,
|
State: domain.AppStateActive,
|
||||||
|
LoginVersion: gu.Ptr(domain.LoginVersionUnspecified),
|
||||||
|
LoginBaseURI: gu.Ptr(""),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
name: "change saml app, ok, loginversion",
|
name: "change saml app, ok, loginversion",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: expectEventstore(
|
eventstore: expectEventstore(
|
||||||
@@ -781,9 +835,9 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
|
|||||||
AppName: "app",
|
AppName: "app",
|
||||||
EntityID: "https://test2.com/saml/metadata",
|
EntityID: "https://test2.com/saml/metadata",
|
||||||
Metadata: testMetadataChangedEntityID,
|
Metadata: testMetadataChangedEntityID,
|
||||||
MetadataURL: "",
|
MetadataURL: gu.Ptr(""),
|
||||||
LoginVersion: domain.LoginVersion2,
|
LoginVersion: gu.Ptr(domain.LoginVersion2),
|
||||||
LoginBaseURI: "https://test.com/login",
|
LoginBaseURI: gu.Ptr("https://test.com/login"),
|
||||||
},
|
},
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
},
|
},
|
||||||
@@ -797,10 +851,10 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
|
|||||||
AppName: "app",
|
AppName: "app",
|
||||||
EntityID: "https://test2.com/saml/metadata",
|
EntityID: "https://test2.com/saml/metadata",
|
||||||
Metadata: testMetadataChangedEntityID,
|
Metadata: testMetadataChangedEntityID,
|
||||||
MetadataURL: "",
|
MetadataURL: gu.Ptr(""),
|
||||||
State: domain.AppStateActive,
|
State: domain.AppStateActive,
|
||||||
LoginVersion: domain.LoginVersion2,
|
LoginVersion: gu.Ptr(domain.LoginVersion2),
|
||||||
LoginBaseURI: "https://test.com/login",
|
LoginBaseURI: gu.Ptr("https://test.com/login"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -808,11 +862,14 @@ func TestCommandSide_ChangeSAMLApplication(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
r := &Commands{
|
r := &Commands{
|
||||||
eventstore: tt.fields.eventstore(t),
|
eventstore: tt.fields.eventstore(t),
|
||||||
httpClient: tt.fields.httpClient,
|
httpClient: tt.fields.httpClient,
|
||||||
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
}
|
}
|
||||||
got, err := r.ChangeSAMLApplication(tt.args.ctx, tt.args.samlApp, tt.args.resourceOwner)
|
got, err := r.UpdateSAMLApplication(tt.args.ctx, tt.args.samlApp, tt.args.resourceOwner)
|
||||||
if tt.res.err == nil {
|
if tt.res.err == nil {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
@@ -8,13 +8,16 @@ import (
|
|||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
|
"github.com/zitadel/zitadel/internal/eventstore/repository/mock"
|
||||||
"github.com/zitadel/zitadel/internal/repository/project"
|
"github.com/zitadel/zitadel/internal/repository/project"
|
||||||
"github.com/zitadel/zitadel/internal/zerrors"
|
"github.com/zitadel/zitadel/internal/zerrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCommandSide_ChangeApplication(t *testing.T) {
|
func TestCommandSide_ChangeApplication(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore *eventstore.Eventstore
|
eventstore func(*testing.T) *eventstore.Eventstore
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@@ -35,9 +38,7 @@ func TestCommandSide_ChangeApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "invalid app missing projectid, invalid argument error",
|
name: "invalid app missing projectid, invalid argument error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(func(mockRepository *mock.MockRepository) {}),
|
||||||
t,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -55,9 +56,7 @@ func TestCommandSide_ChangeApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "invalid app missing appid, invalid argument error",
|
name: "invalid app missing appid, invalid argument error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(func(mockRepository *mock.MockRepository) {}),
|
||||||
t,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -74,9 +73,7 @@ func TestCommandSide_ChangeApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "invalid app missing name, invalid argument error",
|
name: "invalid app missing name, invalid argument error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(func(mockRepository *mock.MockRepository) {}),
|
||||||
t,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -94,10 +91,7 @@ func TestCommandSide_ChangeApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "app not existing, not found error",
|
name: "app not existing, not found error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(expectFilter()),
|
||||||
t,
|
|
||||||
expectFilter(),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -115,8 +109,7 @@ func TestCommandSide_ChangeApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "app name not changed, not found error",
|
name: "app name not changed, not found error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(
|
||||||
t,
|
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
|
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
|
||||||
&project.NewAggregate("project1", "org1").Aggregate,
|
&project.NewAggregate("project1", "org1").Aggregate,
|
||||||
@@ -142,8 +135,14 @@ func TestCommandSide_ChangeApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "app changed, ok",
|
name: "app changed, ok",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(
|
||||||
t,
|
expectFilter(
|
||||||
|
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
|
||||||
|
&project.NewAggregate("project1", "org1").Aggregate,
|
||||||
|
"app1",
|
||||||
|
"app",
|
||||||
|
)),
|
||||||
|
),
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
|
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
|
||||||
&project.NewAggregate("project1", "org1").Aggregate,
|
&project.NewAggregate("project1", "org1").Aggregate,
|
||||||
@@ -179,10 +178,13 @@ func TestCommandSide_ChangeApplication(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
r := &Commands{
|
r := &Commands{
|
||||||
eventstore: tt.fields.eventstore,
|
eventstore: tt.fields.eventstore(t),
|
||||||
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
}
|
}
|
||||||
got, err := r.ChangeApplication(tt.args.ctx, tt.args.projectID, tt.args.app, tt.args.resourceOwner)
|
got, err := r.UpdateApplicationName(tt.args.ctx, tt.args.projectID, tt.args.app, tt.args.resourceOwner)
|
||||||
if tt.res.err == nil {
|
if tt.res.err == nil {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
@@ -197,8 +199,10 @@ func TestCommandSide_ChangeApplication(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCommandSide_DeactivateApplication(t *testing.T) {
|
func TestCommandSide_DeactivateApplication(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore *eventstore.Eventstore
|
eventstore func(*testing.T) *eventstore.Eventstore
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@@ -219,9 +223,7 @@ func TestCommandSide_DeactivateApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "missing projectid, invalid argument error",
|
name: "missing projectid, invalid argument error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(func(mockRepository *mock.MockRepository) {}),
|
||||||
t,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -236,9 +238,7 @@ func TestCommandSide_DeactivateApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "missing appid, invalid argument error",
|
name: "missing appid, invalid argument error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(func(mockRepository *mock.MockRepository) {}),
|
||||||
t,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -253,8 +253,7 @@ func TestCommandSide_DeactivateApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "app not existing, not found error",
|
name: "app not existing, not found error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(
|
||||||
t,
|
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -271,8 +270,7 @@ func TestCommandSide_DeactivateApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "app already inactive, precondition error",
|
name: "app already inactive, precondition error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(
|
||||||
t,
|
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
|
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
|
||||||
&project.NewAggregate("project1", "org1").Aggregate,
|
&project.NewAggregate("project1", "org1").Aggregate,
|
||||||
@@ -299,8 +297,14 @@ func TestCommandSide_DeactivateApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "app deactivate, ok",
|
name: "app deactivate, ok",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(
|
||||||
t,
|
expectFilter(
|
||||||
|
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
|
||||||
|
&project.NewAggregate("project1", "org1").Aggregate,
|
||||||
|
"app1",
|
||||||
|
"app",
|
||||||
|
)),
|
||||||
|
),
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
|
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
|
||||||
&project.NewAggregate("project1", "org1").Aggregate,
|
&project.NewAggregate("project1", "org1").Aggregate,
|
||||||
@@ -331,8 +335,11 @@ func TestCommandSide_DeactivateApplication(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
r := &Commands{
|
r := &Commands{
|
||||||
eventstore: tt.fields.eventstore,
|
eventstore: tt.fields.eventstore(t),
|
||||||
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
}
|
}
|
||||||
got, err := r.DeactivateApplication(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
|
got, err := r.DeactivateApplication(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
|
||||||
if tt.res.err == nil {
|
if tt.res.err == nil {
|
||||||
@@ -349,8 +356,10 @@ func TestCommandSide_DeactivateApplication(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCommandSide_ReactivateApplication(t *testing.T) {
|
func TestCommandSide_ReactivateApplication(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore *eventstore.Eventstore
|
eventstore func(*testing.T) *eventstore.Eventstore
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@@ -371,9 +380,7 @@ func TestCommandSide_ReactivateApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "missing projectid, invalid argument error",
|
name: "missing projectid, invalid argument error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(func(mockRepository *mock.MockRepository) {}),
|
||||||
t,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -388,9 +395,7 @@ func TestCommandSide_ReactivateApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "missing appid, invalid argument error",
|
name: "missing appid, invalid argument error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(func(mockRepository *mock.MockRepository) {}),
|
||||||
t,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -405,10 +410,7 @@ func TestCommandSide_ReactivateApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "app not existing, not found error",
|
name: "app not existing, not found error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(expectFilter()),
|
||||||
t,
|
|
||||||
expectFilter(),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -423,8 +425,7 @@ func TestCommandSide_ReactivateApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "app already active, precondition error",
|
name: "app already active, precondition error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(
|
||||||
t,
|
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
|
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
|
||||||
&project.NewAggregate("project1", "org1").Aggregate,
|
&project.NewAggregate("project1", "org1").Aggregate,
|
||||||
@@ -447,8 +448,7 @@ func TestCommandSide_ReactivateApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "app reactivate, ok",
|
name: "app reactivate, ok",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(
|
||||||
t,
|
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
|
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
|
||||||
&project.NewAggregate("project1", "org1").Aggregate,
|
&project.NewAggregate("project1", "org1").Aggregate,
|
||||||
@@ -483,8 +483,11 @@ func TestCommandSide_ReactivateApplication(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
r := &Commands{
|
r := &Commands{
|
||||||
eventstore: tt.fields.eventstore,
|
eventstore: tt.fields.eventstore(t),
|
||||||
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
}
|
}
|
||||||
got, err := r.ReactivateApplication(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
|
got, err := r.ReactivateApplication(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
|
||||||
if tt.res.err == nil {
|
if tt.res.err == nil {
|
||||||
@@ -501,8 +504,10 @@ func TestCommandSide_ReactivateApplication(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCommandSide_RemoveApplication(t *testing.T) {
|
func TestCommandSide_RemoveApplication(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore *eventstore.Eventstore
|
eventstore func(*testing.T) *eventstore.Eventstore
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@@ -523,9 +528,7 @@ func TestCommandSide_RemoveApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "missing projectid, invalid argument error",
|
name: "missing projectid, invalid argument error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(func(mockRepository *mock.MockRepository) {}),
|
||||||
t,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -540,9 +543,7 @@ func TestCommandSide_RemoveApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "missing appid, invalid argument error",
|
name: "missing appid, invalid argument error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(func(mockRepository *mock.MockRepository) {}),
|
||||||
t,
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -557,10 +558,7 @@ func TestCommandSide_RemoveApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "app not existing, not found error",
|
name: "app not existing, not found error",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(expectFilter()),
|
||||||
t,
|
|
||||||
expectFilter(),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
@@ -575,8 +573,7 @@ func TestCommandSide_RemoveApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "app remove, entityID, ok",
|
name: "app remove, entityID, ok",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(
|
||||||
t,
|
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
|
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
|
||||||
&project.NewAggregate("project1", "org1").Aggregate,
|
&project.NewAggregate("project1", "org1").Aggregate,
|
||||||
@@ -584,6 +581,7 @@ func TestCommandSide_RemoveApplication(t *testing.T) {
|
|||||||
"app",
|
"app",
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
|
expectFilter(),
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
|
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
|
||||||
&project.NewAggregate("project1", "org1").Aggregate,
|
&project.NewAggregate("project1", "org1").Aggregate,
|
||||||
@@ -625,8 +623,7 @@ func TestCommandSide_RemoveApplication(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "app remove, ok",
|
name: "app remove, ok",
|
||||||
fields: fields{
|
fields: fields{
|
||||||
eventstore: eventstoreExpect(
|
eventstore: expectEventstore(
|
||||||
t,
|
|
||||||
expectFilter(
|
expectFilter(
|
||||||
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
|
eventFromEventPusher(project.NewApplicationAddedEvent(context.Background(),
|
||||||
&project.NewAggregate("project1", "org1").Aggregate,
|
&project.NewAggregate("project1", "org1").Aggregate,
|
||||||
@@ -636,6 +633,7 @@ func TestCommandSide_RemoveApplication(t *testing.T) {
|
|||||||
),
|
),
|
||||||
// app is not saml, or no saml config available
|
// app is not saml, or no saml config available
|
||||||
expectFilter(),
|
expectFilter(),
|
||||||
|
expectFilter(),
|
||||||
expectPush(
|
expectPush(
|
||||||
project.NewApplicationRemovedEvent(context.Background(),
|
project.NewApplicationRemovedEvent(context.Background(),
|
||||||
&project.NewAggregate("project1", "org1").Aggregate,
|
&project.NewAggregate("project1", "org1").Aggregate,
|
||||||
@@ -661,8 +659,11 @@ func TestCommandSide_RemoveApplication(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
r := &Commands{
|
r := &Commands{
|
||||||
eventstore: tt.fields.eventstore,
|
eventstore: tt.fields.eventstore(t),
|
||||||
|
checkPermission: newMockPermissionCheckAllowed(),
|
||||||
}
|
}
|
||||||
got, err := r.RemoveApplication(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
|
got, err := r.RemoveApplication(tt.args.ctx, tt.args.projectID, tt.args.appID, tt.args.resourceOwner)
|
||||||
if tt.res.err == nil {
|
if tt.res.err == nil {
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -35,21 +37,21 @@ func oidcWriteModelToOIDCConfig(writeModel *OIDCApplicationWriteModel) *domain.O
|
|||||||
RedirectUris: writeModel.RedirectUris,
|
RedirectUris: writeModel.RedirectUris,
|
||||||
ResponseTypes: writeModel.ResponseTypes,
|
ResponseTypes: writeModel.ResponseTypes,
|
||||||
GrantTypes: writeModel.GrantTypes,
|
GrantTypes: writeModel.GrantTypes,
|
||||||
ApplicationType: writeModel.ApplicationType,
|
ApplicationType: gu.Ptr(writeModel.ApplicationType),
|
||||||
AuthMethodType: writeModel.AuthMethodType,
|
AuthMethodType: gu.Ptr(writeModel.AuthMethodType),
|
||||||
PostLogoutRedirectUris: writeModel.PostLogoutRedirectUris,
|
PostLogoutRedirectUris: writeModel.PostLogoutRedirectUris,
|
||||||
OIDCVersion: writeModel.OIDCVersion,
|
OIDCVersion: gu.Ptr(writeModel.OIDCVersion),
|
||||||
DevMode: writeModel.DevMode,
|
DevMode: gu.Ptr(writeModel.DevMode),
|
||||||
AccessTokenType: writeModel.AccessTokenType,
|
AccessTokenType: gu.Ptr(writeModel.AccessTokenType),
|
||||||
AccessTokenRoleAssertion: writeModel.AccessTokenRoleAssertion,
|
AccessTokenRoleAssertion: gu.Ptr(writeModel.AccessTokenRoleAssertion),
|
||||||
IDTokenRoleAssertion: writeModel.IDTokenRoleAssertion,
|
IDTokenRoleAssertion: gu.Ptr(writeModel.IDTokenRoleAssertion),
|
||||||
IDTokenUserinfoAssertion: writeModel.IDTokenUserinfoAssertion,
|
IDTokenUserinfoAssertion: gu.Ptr(writeModel.IDTokenUserinfoAssertion),
|
||||||
ClockSkew: writeModel.ClockSkew,
|
ClockSkew: gu.Ptr(writeModel.ClockSkew),
|
||||||
AdditionalOrigins: writeModel.AdditionalOrigins,
|
AdditionalOrigins: writeModel.AdditionalOrigins,
|
||||||
SkipNativeAppSuccessPage: writeModel.SkipNativeAppSuccessPage,
|
SkipNativeAppSuccessPage: gu.Ptr(writeModel.SkipNativeAppSuccessPage),
|
||||||
BackChannelLogoutURI: writeModel.BackChannelLogoutURI,
|
BackChannelLogoutURI: gu.Ptr(writeModel.BackChannelLogoutURI),
|
||||||
LoginVersion: writeModel.LoginVersion,
|
LoginVersion: gu.Ptr(writeModel.LoginVersion),
|
||||||
LoginBaseURI: writeModel.LoginBaseURI,
|
LoginBaseURI: gu.Ptr(writeModel.LoginBaseURI),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,10 +62,10 @@ func samlWriteModelToSAMLConfig(writeModel *SAMLApplicationWriteModel) *domain.S
|
|||||||
AppName: writeModel.AppName,
|
AppName: writeModel.AppName,
|
||||||
State: writeModel.State,
|
State: writeModel.State,
|
||||||
Metadata: writeModel.Metadata,
|
Metadata: writeModel.Metadata,
|
||||||
MetadataURL: writeModel.MetadataURL,
|
MetadataURL: gu.Ptr(writeModel.MetadataURL),
|
||||||
EntityID: writeModel.EntityID,
|
EntityID: writeModel.EntityID,
|
||||||
LoginVersion: writeModel.LoginVersion,
|
LoginVersion: gu.Ptr(writeModel.LoginVersion),
|
||||||
LoginBaseURI: writeModel.LoginBaseURI,
|
LoginBaseURI: gu.Ptr(writeModel.LoginBaseURI),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,15 +80,6 @@ func apiWriteModelToAPIConfig(writeModel *APIApplicationWriteModel) *domain.APIA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func roleWriteModelToRole(writeModel *ProjectRoleWriteModel) *domain.ProjectRole {
|
|
||||||
return &domain.ProjectRole{
|
|
||||||
ObjectRoot: writeModelToObjectRoot(writeModel.WriteModel),
|
|
||||||
Key: writeModel.Key,
|
|
||||||
DisplayName: writeModel.DisplayName,
|
|
||||||
Group: writeModel.Group,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func memberWriteModelToProjectGrantMember(writeModel *ProjectGrantMemberWriteModel) *domain.ProjectGrantMember {
|
func memberWriteModelToProjectGrantMember(writeModel *ProjectGrantMemberWriteModel) *domain.ProjectGrantMember {
|
||||||
return &domain.ProjectGrantMember{
|
return &domain.ProjectGrantMember{
|
||||||
ObjectRoot: writeModelToObjectRoot(writeModel.WriteModel),
|
ObjectRoot: writeModelToObjectRoot(writeModel.WriteModel),
|
||||||
|
@@ -2,6 +2,7 @@ package command
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"slices"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
@@ -120,7 +121,7 @@ func (wm *ProjectWriteModel) NewChangedEvent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isProjectStateExists(state domain.ProjectState) bool {
|
func isProjectStateExists(state domain.ProjectState) bool {
|
||||||
return !hasProjectState(state, domain.ProjectStateRemoved, domain.ProjectStateUnspecified)
|
return !slices.Contains([]domain.ProjectState{domain.ProjectStateRemoved, domain.ProjectStateUnspecified}, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProjectAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
|
func ProjectAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
|
||||||
@@ -130,12 +131,3 @@ func ProjectAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggre
|
|||||||
func ProjectAggregateFromWriteModelWithCTX(ctx context.Context, wm *eventstore.WriteModel) *eventstore.Aggregate {
|
func ProjectAggregateFromWriteModelWithCTX(ctx context.Context, wm *eventstore.WriteModel) *eventstore.Aggregate {
|
||||||
return project.AggregateFromWriteModel(ctx, wm)
|
return project.AggregateFromWriteModel(ctx, wm)
|
||||||
}
|
}
|
||||||
|
|
||||||
func hasProjectState(check domain.ProjectState, states ...domain.ProjectState) bool {
|
|
||||||
for _, state := range states {
|
|
||||||
if check == state {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
package domain
|
package domain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -32,22 +33,22 @@ type OIDCApp struct {
|
|||||||
RedirectUris []string
|
RedirectUris []string
|
||||||
ResponseTypes []OIDCResponseType
|
ResponseTypes []OIDCResponseType
|
||||||
GrantTypes []OIDCGrantType
|
GrantTypes []OIDCGrantType
|
||||||
ApplicationType OIDCApplicationType
|
ApplicationType *OIDCApplicationType
|
||||||
AuthMethodType OIDCAuthMethodType
|
AuthMethodType *OIDCAuthMethodType
|
||||||
PostLogoutRedirectUris []string
|
PostLogoutRedirectUris []string
|
||||||
OIDCVersion OIDCVersion
|
OIDCVersion *OIDCVersion
|
||||||
Compliance *Compliance
|
Compliance *Compliance
|
||||||
DevMode bool
|
DevMode *bool
|
||||||
AccessTokenType OIDCTokenType
|
AccessTokenType *OIDCTokenType
|
||||||
AccessTokenRoleAssertion bool
|
AccessTokenRoleAssertion *bool
|
||||||
IDTokenRoleAssertion bool
|
IDTokenRoleAssertion *bool
|
||||||
IDTokenUserinfoAssertion bool
|
IDTokenUserinfoAssertion *bool
|
||||||
ClockSkew time.Duration
|
ClockSkew *time.Duration
|
||||||
AdditionalOrigins []string
|
AdditionalOrigins []string
|
||||||
SkipNativeAppSuccessPage bool
|
SkipNativeAppSuccessPage *bool
|
||||||
BackChannelLogoutURI string
|
BackChannelLogoutURI *string
|
||||||
LoginVersion LoginVersion
|
LoginVersion *LoginVersion
|
||||||
LoginBaseURI string
|
LoginBaseURI *string
|
||||||
|
|
||||||
State AppState
|
State AppState
|
||||||
}
|
}
|
||||||
@@ -69,7 +70,7 @@ func (a *OIDCApp) setClientSecret(encodedHash string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *OIDCApp) requiresClientSecret() bool {
|
func (a *OIDCApp) requiresClientSecret() bool {
|
||||||
return a.AuthMethodType == OIDCAuthMethodTypeBasic || a.AuthMethodType == OIDCAuthMethodTypePost
|
return a.AuthMethodType != nil && (*a.AuthMethodType == OIDCAuthMethodTypeBasic || *a.AuthMethodType == OIDCAuthMethodTypePost)
|
||||||
}
|
}
|
||||||
|
|
||||||
type OIDCVersion int32
|
type OIDCVersion int32
|
||||||
@@ -137,7 +138,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (a *OIDCApp) IsValid() bool {
|
func (a *OIDCApp) IsValid() bool {
|
||||||
if a.ClockSkew > time.Second*5 || a.ClockSkew < time.Second*0 || !a.OriginsValid() {
|
if (a.ClockSkew != nil && (*a.ClockSkew > time.Second*5 || *a.ClockSkew < time.Second*0)) || !a.OriginsValid() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
grantTypes := a.getRequiredGrantTypes()
|
grantTypes := a.getRequiredGrantTypes()
|
||||||
@@ -204,30 +205,25 @@ func ContainsOIDCGrantTypes(shouldContain, list []OIDCGrantType) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func containsOIDCGrantType(grantTypes []OIDCGrantType, grantType OIDCGrantType) bool {
|
func containsOIDCGrantType(grantTypes []OIDCGrantType, grantType OIDCGrantType) bool {
|
||||||
for _, gt := range grantTypes {
|
return slices.Contains(grantTypes, grantType)
|
||||||
if gt == grantType {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *OIDCApp) FillCompliance() {
|
func (a *OIDCApp) FillCompliance() {
|
||||||
a.Compliance = GetOIDCCompliance(a.OIDCVersion, a.ApplicationType, a.GrantTypes, a.ResponseTypes, a.AuthMethodType, a.RedirectUris)
|
a.Compliance = GetOIDCCompliance(a.OIDCVersion, a.ApplicationType, a.GrantTypes, a.ResponseTypes, a.AuthMethodType, a.RedirectUris)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOIDCCompliance(version OIDCVersion, appType OIDCApplicationType, grantTypes []OIDCGrantType, responseTypes []OIDCResponseType, authMethod OIDCAuthMethodType, redirectUris []string) *Compliance {
|
func GetOIDCCompliance(version *OIDCVersion, appType *OIDCApplicationType, grantTypes []OIDCGrantType, responseTypes []OIDCResponseType, authMethod *OIDCAuthMethodType, redirectUris []string) *Compliance {
|
||||||
switch version {
|
if version != nil && *version == OIDCVersionV1 {
|
||||||
case OIDCVersionV1:
|
|
||||||
return GetOIDCV1Compliance(appType, grantTypes, authMethod, redirectUris)
|
return GetOIDCV1Compliance(appType, grantTypes, authMethod, redirectUris)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Compliance{
|
return &Compliance{
|
||||||
NoneCompliant: true,
|
NoneCompliant: true,
|
||||||
Problems: []string{"Application.OIDC.UnsupportedVersion"},
|
Problems: []string{"Application.OIDC.UnsupportedVersion"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOIDCV1Compliance(appType OIDCApplicationType, grantTypes []OIDCGrantType, authMethod OIDCAuthMethodType, redirectUris []string) *Compliance {
|
func GetOIDCV1Compliance(appType *OIDCApplicationType, grantTypes []OIDCGrantType, authMethod *OIDCAuthMethodType, redirectUris []string) *Compliance {
|
||||||
compliance := &Compliance{NoneCompliant: false}
|
compliance := &Compliance{NoneCompliant: false}
|
||||||
|
|
||||||
checkGrantTypesCombination(compliance, grantTypes)
|
checkGrantTypesCombination(compliance, grantTypes)
|
||||||
@@ -247,7 +243,7 @@ func checkGrantTypesCombination(compliance *Compliance, grantTypes []OIDCGrantTy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkRedirectURIs(compliance *Compliance, grantTypes []OIDCGrantType, appType OIDCApplicationType, redirectUris []string) {
|
func checkRedirectURIs(compliance *Compliance, grantTypes []OIDCGrantType, appType *OIDCApplicationType, redirectUris []string) {
|
||||||
// See #5684 for OIDCGrantTypeDeviceCode and redirectUris further explanation
|
// See #5684 for OIDCGrantTypeDeviceCode and redirectUris further explanation
|
||||||
if len(redirectUris) == 0 && (!containsOIDCGrantType(grantTypes, OIDCGrantTypeDeviceCode) || (containsOIDCGrantType(grantTypes, OIDCGrantTypeDeviceCode) && containsOIDCGrantType(grantTypes, OIDCGrantTypeAuthorizationCode))) {
|
if len(redirectUris) == 0 && (!containsOIDCGrantType(grantTypes, OIDCGrantTypeDeviceCode) || (containsOIDCGrantType(grantTypes, OIDCGrantTypeDeviceCode) && containsOIDCGrantType(grantTypes, OIDCGrantTypeAuthorizationCode))) {
|
||||||
compliance.NoneCompliant = true
|
compliance.NoneCompliant = true
|
||||||
@@ -266,53 +262,58 @@ func checkRedirectURIs(compliance *Compliance, grantTypes []OIDCGrantType, appTy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkApplicationType(compliance *Compliance, appType OIDCApplicationType, authMethod OIDCAuthMethodType) {
|
func checkApplicationType(compliance *Compliance, appType *OIDCApplicationType, authMethod *OIDCAuthMethodType) {
|
||||||
switch appType {
|
if appType != nil {
|
||||||
case OIDCApplicationTypeNative:
|
switch *appType {
|
||||||
GetOIDCV1NativeApplicationCompliance(compliance, authMethod)
|
case OIDCApplicationTypeNative:
|
||||||
case OIDCApplicationTypeUserAgent:
|
GetOIDCV1NativeApplicationCompliance(compliance, authMethod)
|
||||||
GetOIDCV1UserAgentApplicationCompliance(compliance, authMethod)
|
case OIDCApplicationTypeUserAgent:
|
||||||
|
GetOIDCV1UserAgentApplicationCompliance(compliance, authMethod)
|
||||||
|
case OIDCApplicationTypeWeb:
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if compliance.NoneCompliant {
|
if compliance.NoneCompliant {
|
||||||
compliance.Problems = append([]string{"Application.OIDC.V1.NotCompliant"}, compliance.Problems...)
|
compliance.Problems = append([]string{"Application.OIDC.V1.NotCompliant"}, compliance.Problems...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOIDCV1NativeApplicationCompliance(compliance *Compliance, authMethod OIDCAuthMethodType) {
|
func GetOIDCV1NativeApplicationCompliance(compliance *Compliance, authMethod *OIDCAuthMethodType) {
|
||||||
if authMethod != OIDCAuthMethodTypeNone {
|
if authMethod != nil && *authMethod != OIDCAuthMethodTypeNone {
|
||||||
compliance.NoneCompliant = true
|
compliance.NoneCompliant = true
|
||||||
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Native.AuthMethodType.NotNone")
|
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Native.AuthMethodType.NotNone")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOIDCV1UserAgentApplicationCompliance(compliance *Compliance, authMethod OIDCAuthMethodType) {
|
func GetOIDCV1UserAgentApplicationCompliance(compliance *Compliance, authMethod *OIDCAuthMethodType) {
|
||||||
if authMethod != OIDCAuthMethodTypeNone {
|
if authMethod != nil && *authMethod != OIDCAuthMethodTypeNone {
|
||||||
compliance.NoneCompliant = true
|
compliance.NoneCompliant = true
|
||||||
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.UserAgent.AuthMethodType.NotNone")
|
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.UserAgent.AuthMethodType.NotNone")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckRedirectUrisCode(compliance *Compliance, appType OIDCApplicationType, redirectUris []string) {
|
func CheckRedirectUrisCode(compliance *Compliance, appType *OIDCApplicationType, redirectUris []string) {
|
||||||
if urlsAreHttps(redirectUris) {
|
if urlsAreHttps(redirectUris) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if urlContainsPrefix(redirectUris, http) {
|
if urlContainsPrefix(redirectUris, http) {
|
||||||
if appType == OIDCApplicationTypeUserAgent {
|
if appType != nil && *appType == OIDCApplicationTypeUserAgent {
|
||||||
compliance.NoneCompliant = true
|
compliance.NoneCompliant = true
|
||||||
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Code.RedirectUris.HttpOnlyForWeb")
|
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Code.RedirectUris.HttpOnlyForWeb")
|
||||||
}
|
}
|
||||||
if appType == OIDCApplicationTypeNative && !onlyLocalhostIsHttp(redirectUris) {
|
if appType != nil && *appType == OIDCApplicationTypeNative && !onlyLocalhostIsHttp(redirectUris) {
|
||||||
compliance.NoneCompliant = true
|
compliance.NoneCompliant = true
|
||||||
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Native.RedirectUris.MustBeHttpLocalhost")
|
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Native.RedirectUris.MustBeHttpLocalhost")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if containsCustom(redirectUris) && appType != OIDCApplicationTypeNative {
|
if containsCustom(redirectUris) && appType != nil && *appType != OIDCApplicationTypeNative {
|
||||||
compliance.NoneCompliant = true
|
compliance.NoneCompliant = true
|
||||||
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Code.RedirectUris.CustomOnlyForNative")
|
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Code.RedirectUris.CustomOnlyForNative")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckRedirectUrisImplicit(compliance *Compliance, appType OIDCApplicationType, redirectUris []string) {
|
func CheckRedirectUrisImplicit(compliance *Compliance, appType *OIDCApplicationType, redirectUris []string) {
|
||||||
if urlsAreHttps(redirectUris) {
|
if urlsAreHttps(redirectUris) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -321,7 +322,7 @@ func CheckRedirectUrisImplicit(compliance *Compliance, appType OIDCApplicationTy
|
|||||||
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Implicit.RedirectUris.CustomNotAllowed")
|
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Implicit.RedirectUris.CustomNotAllowed")
|
||||||
}
|
}
|
||||||
if urlContainsPrefix(redirectUris, http) {
|
if urlContainsPrefix(redirectUris, http) {
|
||||||
if appType == OIDCApplicationTypeNative {
|
if appType != nil && *appType == OIDCApplicationTypeNative {
|
||||||
if !onlyLocalhostIsHttp(redirectUris) {
|
if !onlyLocalhostIsHttp(redirectUris) {
|
||||||
compliance.NoneCompliant = true
|
compliance.NoneCompliant = true
|
||||||
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Native.RedirectUris.MustBeHttpLocalhost")
|
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Native.RedirectUris.MustBeHttpLocalhost")
|
||||||
@@ -333,20 +334,20 @@ func CheckRedirectUrisImplicit(compliance *Compliance, appType OIDCApplicationTy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckRedirectUrisImplicitAndCode(compliance *Compliance, appType OIDCApplicationType, redirectUris []string) {
|
func CheckRedirectUrisImplicitAndCode(compliance *Compliance, appType *OIDCApplicationType, redirectUris []string) {
|
||||||
if urlsAreHttps(redirectUris) {
|
if urlsAreHttps(redirectUris) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if containsCustom(redirectUris) && appType != OIDCApplicationTypeNative {
|
if containsCustom(redirectUris) && appType != nil && *appType != OIDCApplicationTypeNative {
|
||||||
compliance.NoneCompliant = true
|
compliance.NoneCompliant = true
|
||||||
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Implicit.RedirectUris.CustomNotAllowed")
|
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Implicit.RedirectUris.CustomNotAllowed")
|
||||||
}
|
}
|
||||||
if urlContainsPrefix(redirectUris, http) {
|
if urlContainsPrefix(redirectUris, http) {
|
||||||
if appType == OIDCApplicationTypeUserAgent {
|
if appType != nil && *appType == OIDCApplicationTypeUserAgent {
|
||||||
compliance.NoneCompliant = true
|
compliance.NoneCompliant = true
|
||||||
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Code.RedirectUris.HttpOnlyForWeb")
|
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Code.RedirectUris.HttpOnlyForWeb")
|
||||||
}
|
}
|
||||||
if !onlyLocalhostIsHttp(redirectUris) && appType == OIDCApplicationTypeNative {
|
if !onlyLocalhostIsHttp(redirectUris) && appType != nil && *appType == OIDCApplicationTypeNative {
|
||||||
compliance.NoneCompliant = true
|
compliance.NoneCompliant = true
|
||||||
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Native.RedirectUris.MustBeHttpLocalhost")
|
compliance.Problems = append(compliance.Problems, "Application.OIDC.V1.Native.RedirectUris.MustBeHttpLocalhost")
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -25,7 +27,7 @@ func TestApplicationValid(t *testing.T) {
|
|||||||
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
|
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
|
||||||
AppID: "AppID",
|
AppID: "AppID",
|
||||||
AppName: "AppName",
|
AppName: "AppName",
|
||||||
ClockSkew: time.Minute * 1,
|
ClockSkew: gu.Ptr(time.Minute * 1),
|
||||||
ResponseTypes: []OIDCResponseType{OIDCResponseTypeCode},
|
ResponseTypes: []OIDCResponseType{OIDCResponseTypeCode},
|
||||||
GrantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode},
|
GrantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode},
|
||||||
},
|
},
|
||||||
@@ -39,7 +41,7 @@ func TestApplicationValid(t *testing.T) {
|
|||||||
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
|
ObjectRoot: models.ObjectRoot{AggregateID: "AggregateID"},
|
||||||
AppID: "AppID",
|
AppID: "AppID",
|
||||||
AppName: "AppName",
|
AppName: "AppName",
|
||||||
ClockSkew: time.Minute * -1,
|
ClockSkew: gu.Ptr(time.Minute * -1),
|
||||||
ResponseTypes: []OIDCResponseType{OIDCResponseTypeCode},
|
ResponseTypes: []OIDCResponseType{OIDCResponseTypeCode},
|
||||||
GrantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode},
|
GrantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode},
|
||||||
},
|
},
|
||||||
@@ -190,9 +192,9 @@ func TestApplicationValid(t *testing.T) {
|
|||||||
|
|
||||||
func TestGetOIDCV1Compliance(t *testing.T) {
|
func TestGetOIDCV1Compliance(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
appType OIDCApplicationType
|
appType *OIDCApplicationType
|
||||||
grantTypes []OIDCGrantType
|
grantTypes []OIDCGrantType
|
||||||
authMethod OIDCAuthMethodType
|
authMethod *OIDCAuthMethodType
|
||||||
redirectUris []string
|
redirectUris []string
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -266,7 +268,7 @@ func Test_checkGrantTypesCombination(t *testing.T) {
|
|||||||
func Test_checkRedirectURIs(t *testing.T) {
|
func Test_checkRedirectURIs(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
grantTypes []OIDCGrantType
|
grantTypes []OIDCGrantType
|
||||||
appType OIDCApplicationType
|
appType *OIDCApplicationType
|
||||||
redirectUris []string
|
redirectUris []string
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -304,7 +306,7 @@ func Test_checkRedirectURIs(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
redirectUris: []string{"http://redirect.to/me"},
|
redirectUris: []string{"http://redirect.to/me"},
|
||||||
grantTypes: []OIDCGrantType{OIDCGrantTypeImplicit},
|
grantTypes: []OIDCGrantType{OIDCGrantTypeImplicit},
|
||||||
appType: OIDCApplicationTypeUserAgent,
|
appType: gu.Ptr(OIDCApplicationTypeUserAgent),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -316,7 +318,7 @@ func Test_checkRedirectURIs(t *testing.T) {
|
|||||||
args: args{
|
args: args{
|
||||||
redirectUris: []string{"http://redirect.to/me"},
|
redirectUris: []string{"http://redirect.to/me"},
|
||||||
grantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode},
|
grantTypes: []OIDCGrantType{OIDCGrantTypeAuthorizationCode},
|
||||||
appType: OIDCApplicationTypeUserAgent,
|
appType: gu.Ptr(OIDCApplicationTypeUserAgent),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -338,7 +340,7 @@ func Test_checkRedirectURIs(t *testing.T) {
|
|||||||
|
|
||||||
func Test_CheckRedirectUrisImplicitAndCode(t *testing.T) {
|
func Test_CheckRedirectUrisImplicitAndCode(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
appType OIDCApplicationType
|
appType *OIDCApplicationType
|
||||||
redirectUris []string
|
redirectUris []string
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -356,17 +358,6 @@ func Test_CheckRedirectUrisImplicitAndCode(t *testing.T) {
|
|||||||
redirectUris: []string{"https://redirect.to/me"},
|
redirectUris: []string{"https://redirect.to/me"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// name: "custom protocol, not native",
|
|
||||||
// want: &Compliance{
|
|
||||||
// NoneCompliant: true,
|
|
||||||
// Problems: []string{"Application.OIDC.V1.Implicit.RedirectUris.CustomNotAllowed"},
|
|
||||||
// },
|
|
||||||
// args: args{
|
|
||||||
// redirectUris: []string{"protocol://redirect.to/me"},
|
|
||||||
// appType: OIDCApplicationTypeWeb,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@@ -386,7 +377,7 @@ func Test_CheckRedirectUrisImplicitAndCode(t *testing.T) {
|
|||||||
|
|
||||||
func TestCheckRedirectUrisImplicitAndCode(t *testing.T) {
|
func TestCheckRedirectUrisImplicitAndCode(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
appType OIDCApplicationType
|
appType *OIDCApplicationType
|
||||||
redirectUris []string
|
redirectUris []string
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -402,7 +393,7 @@ func TestCheckRedirectUrisImplicitAndCode(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "custom protocol not native app",
|
name: "custom protocol not native app",
|
||||||
args: args{
|
args: args{
|
||||||
appType: OIDCApplicationTypeWeb,
|
appType: gu.Ptr(OIDCApplicationTypeWeb),
|
||||||
redirectUris: []string{"custom://nirvana.com"},
|
redirectUris: []string{"custom://nirvana.com"},
|
||||||
},
|
},
|
||||||
want: &Compliance{
|
want: &Compliance{
|
||||||
@@ -413,7 +404,7 @@ func TestCheckRedirectUrisImplicitAndCode(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "http localhost user agent app",
|
name: "http localhost user agent app",
|
||||||
args: args{
|
args: args{
|
||||||
appType: OIDCApplicationTypeUserAgent,
|
appType: gu.Ptr(OIDCApplicationTypeUserAgent),
|
||||||
redirectUris: []string{"http://localhost:9009"},
|
redirectUris: []string{"http://localhost:9009"},
|
||||||
},
|
},
|
||||||
want: &Compliance{
|
want: &Compliance{
|
||||||
@@ -424,7 +415,7 @@ func TestCheckRedirectUrisImplicitAndCode(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "http, not only localhost native app",
|
name: "http, not only localhost native app",
|
||||||
args: args{
|
args: args{
|
||||||
appType: OIDCApplicationTypeNative,
|
appType: gu.Ptr(OIDCApplicationTypeNative),
|
||||||
redirectUris: []string{"http://nirvana.com", "http://localhost:9009"},
|
redirectUris: []string{"http://nirvana.com", "http://localhost:9009"},
|
||||||
},
|
},
|
||||||
want: &Compliance{
|
want: &Compliance{
|
||||||
@@ -435,7 +426,7 @@ func TestCheckRedirectUrisImplicitAndCode(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "not allowed combination",
|
name: "not allowed combination",
|
||||||
args: args{
|
args: args{
|
||||||
appType: OIDCApplicationTypeNative,
|
appType: gu.Ptr(OIDCApplicationTypeNative),
|
||||||
redirectUris: []string{"https://nirvana.com", "cutom://nirvana.com"},
|
redirectUris: []string{"https://nirvana.com", "cutom://nirvana.com"},
|
||||||
},
|
},
|
||||||
want: &Compliance{
|
want: &Compliance{
|
||||||
@@ -461,7 +452,7 @@ func TestCheckRedirectUrisImplicitAndCode(t *testing.T) {
|
|||||||
|
|
||||||
func TestCheckRedirectUrisImplicit(t *testing.T) {
|
func TestCheckRedirectUrisImplicit(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
appType OIDCApplicationType
|
appType *OIDCApplicationType
|
||||||
redirectUris []string
|
redirectUris []string
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -488,7 +479,7 @@ func TestCheckRedirectUrisImplicit(t *testing.T) {
|
|||||||
name: "only http protocol, app type native, not only localhost",
|
name: "only http protocol, app type native, not only localhost",
|
||||||
args: args{
|
args: args{
|
||||||
redirectUris: []string{"http://nirvana.com"},
|
redirectUris: []string{"http://nirvana.com"},
|
||||||
appType: OIDCApplicationTypeNative,
|
appType: gu.Ptr(OIDCApplicationTypeNative),
|
||||||
},
|
},
|
||||||
want: &Compliance{
|
want: &Compliance{
|
||||||
NoneCompliant: true,
|
NoneCompliant: true,
|
||||||
@@ -499,7 +490,7 @@ func TestCheckRedirectUrisImplicit(t *testing.T) {
|
|||||||
name: "only http protocol, app type native, only localhost",
|
name: "only http protocol, app type native, only localhost",
|
||||||
args: args{
|
args: args{
|
||||||
redirectUris: []string{"http://localhost:8080"},
|
redirectUris: []string{"http://localhost:8080"},
|
||||||
appType: OIDCApplicationTypeNative,
|
appType: gu.Ptr(OIDCApplicationTypeNative),
|
||||||
},
|
},
|
||||||
want: &Compliance{
|
want: &Compliance{
|
||||||
NoneCompliant: false,
|
NoneCompliant: false,
|
||||||
@@ -510,7 +501,7 @@ func TestCheckRedirectUrisImplicit(t *testing.T) {
|
|||||||
name: "only http protocol, app type web",
|
name: "only http protocol, app type web",
|
||||||
args: args{
|
args: args{
|
||||||
redirectUris: []string{"http://nirvana.com"},
|
redirectUris: []string{"http://nirvana.com"},
|
||||||
appType: OIDCApplicationTypeWeb,
|
appType: gu.Ptr(OIDCApplicationTypeWeb),
|
||||||
},
|
},
|
||||||
want: &Compliance{
|
want: &Compliance{
|
||||||
NoneCompliant: true,
|
NoneCompliant: true,
|
||||||
@@ -535,7 +526,7 @@ func TestCheckRedirectUrisImplicit(t *testing.T) {
|
|||||||
|
|
||||||
func TestCheckRedirectUrisCode(t *testing.T) {
|
func TestCheckRedirectUrisCode(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
appType OIDCApplicationType
|
appType *OIDCApplicationType
|
||||||
redirectUris []string
|
redirectUris []string
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -552,7 +543,7 @@ func TestCheckRedirectUrisCode(t *testing.T) {
|
|||||||
name: "custom prefix, app type web",
|
name: "custom prefix, app type web",
|
||||||
args: args{
|
args: args{
|
||||||
redirectUris: []string{"custom://nirvana.com"},
|
redirectUris: []string{"custom://nirvana.com"},
|
||||||
appType: OIDCApplicationTypeWeb,
|
appType: gu.Ptr(OIDCApplicationTypeWeb),
|
||||||
},
|
},
|
||||||
want: &Compliance{
|
want: &Compliance{
|
||||||
NoneCompliant: true,
|
NoneCompliant: true,
|
||||||
@@ -563,7 +554,7 @@ func TestCheckRedirectUrisCode(t *testing.T) {
|
|||||||
name: "only http protocol, app type user agent",
|
name: "only http protocol, app type user agent",
|
||||||
args: args{
|
args: args{
|
||||||
redirectUris: []string{"http://nirvana.com"},
|
redirectUris: []string{"http://nirvana.com"},
|
||||||
appType: OIDCApplicationTypeUserAgent,
|
appType: gu.Ptr(OIDCApplicationTypeUserAgent),
|
||||||
},
|
},
|
||||||
want: &Compliance{
|
want: &Compliance{
|
||||||
NoneCompliant: true,
|
NoneCompliant: true,
|
||||||
@@ -574,7 +565,7 @@ func TestCheckRedirectUrisCode(t *testing.T) {
|
|||||||
name: "only http protocol, app type native, only localhost",
|
name: "only http protocol, app type native, only localhost",
|
||||||
args: args{
|
args: args{
|
||||||
redirectUris: []string{"http://localhost:8080", "http://nirvana.com:8080"},
|
redirectUris: []string{"http://localhost:8080", "http://nirvana.com:8080"},
|
||||||
appType: OIDCApplicationTypeNative,
|
appType: gu.Ptr(OIDCApplicationTypeNative),
|
||||||
},
|
},
|
||||||
want: &Compliance{
|
want: &Compliance{
|
||||||
NoneCompliant: true,
|
NoneCompliant: true,
|
||||||
@@ -585,7 +576,7 @@ func TestCheckRedirectUrisCode(t *testing.T) {
|
|||||||
name: "custom protocol, not native",
|
name: "custom protocol, not native",
|
||||||
args: args{
|
args: args{
|
||||||
redirectUris: []string{"custom://nirvana.com"},
|
redirectUris: []string{"custom://nirvana.com"},
|
||||||
appType: OIDCApplicationTypeWeb,
|
appType: gu.Ptr(OIDCApplicationTypeWeb),
|
||||||
},
|
},
|
||||||
want: &Compliance{
|
want: &Compliance{
|
||||||
NoneCompliant: true,
|
NoneCompliant: true,
|
||||||
|
@@ -11,9 +11,9 @@ type SAMLApp struct {
|
|||||||
AppName string
|
AppName string
|
||||||
EntityID string
|
EntityID string
|
||||||
Metadata []byte
|
Metadata []byte
|
||||||
MetadataURL string
|
MetadataURL *string
|
||||||
LoginVersion LoginVersion
|
LoginVersion *LoginVersion
|
||||||
LoginBaseURI string
|
LoginBaseURI *string
|
||||||
|
|
||||||
State AppState
|
State AppState
|
||||||
}
|
}
|
||||||
@@ -31,11 +31,14 @@ func (a *SAMLApp) GetMetadata() []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *SAMLApp) GetMetadataURL() string {
|
func (a *SAMLApp) GetMetadataURL() string {
|
||||||
return a.MetadataURL
|
if a.MetadataURL != nil {
|
||||||
|
return *a.MetadataURL
|
||||||
|
}
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *SAMLApp) IsValid() bool {
|
func (a *SAMLApp) IsValid() bool {
|
||||||
if a.MetadataURL == "" && a.Metadata == nil {
|
if (a.MetadataURL == nil || *a.MetadataURL == "") && a.Metadata == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@@ -47,6 +47,9 @@ const (
|
|||||||
PermissionProjectRoleWrite = "project.role.write"
|
PermissionProjectRoleWrite = "project.role.write"
|
||||||
PermissionProjectRoleRead = "project.role.read"
|
PermissionProjectRoleRead = "project.role.read"
|
||||||
PermissionProjectRoleDelete = "project.role.delete"
|
PermissionProjectRoleDelete = "project.role.delete"
|
||||||
|
PermissionProjectAppWrite = "project.app.write"
|
||||||
|
PermissionProjectAppDelete = "project.app.delete"
|
||||||
|
PermissionProjectAppRead = "project.app.read"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProjectPermissionCheck is used as a check for preconditions dependent on application, project, user resourceowner and usergrants.
|
// ProjectPermissionCheck is used as a check for preconditions dependent on application, project, user resourceowner and usergrants.
|
||||||
|
@@ -22,6 +22,7 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/integration/scim"
|
"github.com/zitadel/zitadel/internal/integration/scim"
|
||||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta"
|
action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta"
|
||||||
"github.com/zitadel/zitadel/pkg/grpc/admin"
|
"github.com/zitadel/zitadel/pkg/grpc/admin"
|
||||||
|
app "github.com/zitadel/zitadel/pkg/grpc/app/v2beta"
|
||||||
"github.com/zitadel/zitadel/pkg/grpc/auth"
|
"github.com/zitadel/zitadel/pkg/grpc/auth"
|
||||||
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||||
feature_v2beta "github.com/zitadel/zitadel/pkg/grpc/feature/v2beta"
|
feature_v2beta "github.com/zitadel/zitadel/pkg/grpc/feature/v2beta"
|
||||||
@@ -75,6 +76,7 @@ type Client struct {
|
|||||||
SCIM *scim.Client
|
SCIM *scim.Client
|
||||||
Projectv2Beta project_v2beta.ProjectServiceClient
|
Projectv2Beta project_v2beta.ProjectServiceClient
|
||||||
InstanceV2Beta instance.InstanceServiceClient
|
InstanceV2Beta instance.InstanceServiceClient
|
||||||
|
AppV2Beta app.AppServiceClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultClient(ctx context.Context) (*Client, error) {
|
func NewDefaultClient(ctx context.Context) (*Client, error) {
|
||||||
@@ -114,6 +116,7 @@ func newClient(ctx context.Context, target string) (*Client, error) {
|
|||||||
SCIM: scim.NewScimClient(target),
|
SCIM: scim.NewScimClient(target),
|
||||||
Projectv2Beta: project_v2beta.NewProjectServiceClient(cc),
|
Projectv2Beta: project_v2beta.NewProjectServiceClient(cc),
|
||||||
InstanceV2Beta: instance.NewInstanceServiceClient(cc),
|
InstanceV2Beta: instance.NewInstanceServiceClient(cc),
|
||||||
|
AppV2Beta: app.NewAppServiceClient(cc),
|
||||||
}
|
}
|
||||||
return client, client.pollHealth(ctx)
|
return client, client.pollHealth(ctx)
|
||||||
}
|
}
|
||||||
|
@@ -3,6 +3,8 @@ package model
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/crypto"
|
"github.com/zitadel/zitadel/internal/crypto"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||||
@@ -98,7 +100,7 @@ func GetOIDCCompliance(version OIDCVersion, appType OIDCApplicationType, grantTy
|
|||||||
for i, grantType := range grantTypes {
|
for i, grantType := range grantTypes {
|
||||||
domainGrantTypes[i] = domain.OIDCGrantType(grantType)
|
domainGrantTypes[i] = domain.OIDCGrantType(grantType)
|
||||||
}
|
}
|
||||||
compliance := domain.GetOIDCV1Compliance(domain.OIDCApplicationType(appType), domainGrantTypes, domain.OIDCAuthMethodType(authMethod), redirectUris)
|
compliance := domain.GetOIDCV1Compliance(gu.Ptr(domain.OIDCApplicationType(appType)), domainGrantTypes, gu.Ptr(domain.OIDCAuthMethodType(authMethod)), redirectUris)
|
||||||
return &Compliance{
|
return &Compliance{
|
||||||
NoneCompliant: compliance.NoneCompliant,
|
NoneCompliant: compliance.NoneCompliant,
|
||||||
Problems: compliance.Problems,
|
Problems: compliance.Problems,
|
||||||
|
@@ -5,9 +5,11 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"errors"
|
"errors"
|
||||||
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
sq "github.com/Masterminds/squirrel"
|
sq "github.com/Masterminds/squirrel"
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
"github.com/zitadel/logging"
|
"github.com/zitadel/logging"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/authz"
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
@@ -307,6 +309,19 @@ func (q *Queries) AppByProjectAndAppID(ctx context.Context, shouldTriggerBulk bo
|
|||||||
return app, err
|
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) {
|
func (q *Queries) AppByID(ctx context.Context, appID string, activeOnly bool) (app *App, err error) {
|
||||||
ctx, span := tracing.NewSpan(ctx)
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
defer func() { span.EndWithError(err) }()
|
defer func() { span.EndWithError(err) }()
|
||||||
@@ -476,11 +491,54 @@ func (q *Queries) AppByOIDCClientID(ctx context.Context, clientID string) (app *
|
|||||||
return app, err
|
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)
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
defer func() { span.EndWithError(err) }()
|
defer func() { span.EndWithError(err) }()
|
||||||
|
|
||||||
query, scan := prepareAppsQuery()
|
query, scan := prepareAppsQuery()
|
||||||
|
query = appPermissionCheckV2(ctx, query, isPermissionV2Enabled, queries)
|
||||||
|
|
||||||
eq := sq.Eq{AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()}
|
eq := sq.Eq{AppColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID()}
|
||||||
stmt, args, err := queries.toQuery(query).Where(eq).ToSql()
|
stmt, args, err := queries.toQuery(query).Where(eq).ToSql()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -498,6 +556,21 @@ func (q *Queries) SearchApps(ctx context.Context, queries *AppSearchQueries, wit
|
|||||||
return apps, err
|
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) {
|
func (q *Queries) SearchClientIDs(ctx context.Context, queries *AppSearchQueries, shouldTriggerBulk bool) (ids []string, err error) {
|
||||||
ctx, span := tracing.NewSpan(ctx)
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
defer func() { span.EndWithError(err) }()
|
defer func() { span.EndWithError(err) }()
|
||||||
@@ -574,10 +647,25 @@ func (q *Queries) SAMLAppLoginVersion(ctx context.Context, appID string) (loginV
|
|||||||
return loginVersion, nil
|
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) {
|
func NewAppNameSearchQuery(method TextComparison, value string) (SearchQuery, error) {
|
||||||
return NewTextQuery(AppColumnName, value, method)
|
return NewTextQuery(AppColumnName, value, method)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewAppStateSearchQuery(value domain.AppState) (SearchQuery, error) {
|
||||||
|
return NewNumberQuery(AppColumnState, int(value), NumberEquals)
|
||||||
|
}
|
||||||
|
|
||||||
func NewAppProjectIDSearchQuery(id string) (SearchQuery, error) {
|
func NewAppProjectIDSearchQuery(id string) (SearchQuery, error) {
|
||||||
return NewTextQuery(AppColumnProjectID, id, TextEquals)
|
return NewTextQuery(AppColumnProjectID, id, TextEquals)
|
||||||
}
|
}
|
||||||
@@ -1089,7 +1177,7 @@ func (c sqlOIDCConfig) set(app *App) {
|
|||||||
if c.loginBaseURI.Valid {
|
if c.loginBaseURI.Valid {
|
||||||
app.OIDCConfig.LoginBaseURI = &c.loginBaseURI.String
|
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
|
app.OIDCConfig.ComplianceProblems = compliance.Problems
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
5
pkg/grpc/app/v2beta/application.go
Normal file
5
pkg/grpc/app/v2beta/application.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
type ApplicationConfig = isApplication_Config
|
||||||
|
|
||||||
|
type MetaType = isUpdateSAMLApplicationConfigurationRequest_Metadata
|
26
proto/zitadel/app/v2beta/api.proto
Normal file
26
proto/zitadel/app/v2beta/api.proto
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package zitadel.app.v2beta;
|
||||||
|
|
||||||
|
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/zitadel/zitadel/pkg/grpc/app/v2beta;app";
|
||||||
|
|
||||||
|
enum APIAuthMethodType {
|
||||||
|
API_AUTH_METHOD_TYPE_BASIC = 0;
|
||||||
|
API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message APIConfig {
|
||||||
|
string client_id = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"69629023906488334@ZITADEL\"";
|
||||||
|
description: "generated oauth2/oidc client_id";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
APIAuthMethodType auth_method_type = 2 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "defines how the API passes the login credentials";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
94
proto/zitadel/app/v2beta/app.proto
Normal file
94
proto/zitadel/app/v2beta/app.proto
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package zitadel.app.v2beta;
|
||||||
|
|
||||||
|
import "zitadel/app/v2beta/oidc.proto";
|
||||||
|
import "zitadel/app/v2beta/saml.proto";
|
||||||
|
import "zitadel/app/v2beta/api.proto";
|
||||||
|
import "zitadel/filter/v2/filter.proto";
|
||||||
|
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||||
|
import "google/protobuf/duration.proto";
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
import "validate/validate.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/zitadel/zitadel/pkg/grpc/app/v2beta;app";
|
||||||
|
|
||||||
|
message Application {
|
||||||
|
string id = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"69629023906488334\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// 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\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// The timestamp of the app update.
|
||||||
|
google.protobuf.Timestamp change_date = 3 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2024-12-18T07:50:47.492Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
AppState state = 4 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "current state of the application";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
string name = 5 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"Console\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
oneof config {
|
||||||
|
OIDCConfig oidc_config = 6;
|
||||||
|
APIConfig api_config = 7;
|
||||||
|
SAMLConfig saml_config = 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AppState {
|
||||||
|
APP_STATE_UNSPECIFIED = 0;
|
||||||
|
APP_STATE_ACTIVE = 1;
|
||||||
|
APP_STATE_INACTIVE = 2;
|
||||||
|
APP_STATE_REMOVED = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AppSorting {
|
||||||
|
APP_SORT_BY_ID = 0;
|
||||||
|
APP_SORT_BY_NAME = 1;
|
||||||
|
APP_SORT_BY_STATE = 2;
|
||||||
|
APP_SORT_BY_CREATION_DATE = 3;
|
||||||
|
APP_SORT_BY_CHANGE_DATE = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ApplicationSearchFilter {
|
||||||
|
oneof filter {
|
||||||
|
option (validate.required) = true;
|
||||||
|
ApplicationNameQuery name_filter = 1;
|
||||||
|
AppState state_filter = 2;
|
||||||
|
bool api_app_only = 3;
|
||||||
|
bool oidc_app_only = 4;
|
||||||
|
bool saml_app_only = 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message ApplicationNameQuery {
|
||||||
|
string name = 1 [
|
||||||
|
(validate.rules).string = {max_len: 200},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"Conso\""
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
zitadel.filter.v2.TextFilterMethod method = 2 [
|
||||||
|
(validate.rules).enum.defined_only = true,
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "defines which text equality method is used"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
788
proto/zitadel/app/v2beta/app_service.proto
Normal file
788
proto/zitadel/app/v2beta/app_service.proto
Normal file
@@ -0,0 +1,788 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package zitadel.app.v2beta;
|
||||||
|
|
||||||
|
import "google/api/annotations.proto";
|
||||||
|
import "google/api/field_behavior.proto";
|
||||||
|
import "google/protobuf/duration.proto";
|
||||||
|
import "google/protobuf/struct.proto";
|
||||||
|
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||||
|
import "validate/validate.proto";
|
||||||
|
import "zitadel/app/v2beta/login.proto";
|
||||||
|
import "zitadel/app/v2beta/oidc.proto";
|
||||||
|
import "zitadel/app/v2beta/api.proto";
|
||||||
|
import "zitadel/app/v2beta/app.proto";
|
||||||
|
import "google/protobuf/timestamp.proto";
|
||||||
|
import "zitadel/protoc_gen_zitadel/v2/options.proto";
|
||||||
|
import "zitadel/filter/v2/filter.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/zitadel/zitadel/pkg/grpc/app/v2beta;app";
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
|
||||||
|
info: {
|
||||||
|
title: "Application Service";
|
||||||
|
version: "2.0-beta";
|
||||||
|
description: "This API is intended to manage apps (SAML, OIDC, etc..) in a ZITADEL instance. This service is in beta state. It can AND will continue breaking until a stable version is released.";
|
||||||
|
contact:{
|
||||||
|
name: "ZITADEL"
|
||||||
|
url: "https://zitadel.com"
|
||||||
|
email: "hi@zitadel.com"
|
||||||
|
}
|
||||||
|
license: {
|
||||||
|
name: "Apache 2.0",
|
||||||
|
url: "https://github.com/zitadel/zitadel/blob/main/LICENSING.md";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
schemes: HTTPS;
|
||||||
|
schemes: HTTP;
|
||||||
|
|
||||||
|
consumes: "application/json";
|
||||||
|
consumes: "application/grpc";
|
||||||
|
|
||||||
|
produces: "application/json";
|
||||||
|
produces: "application/grpc";
|
||||||
|
|
||||||
|
consumes: "application/grpc-web+proto";
|
||||||
|
produces: "application/grpc-web+proto";
|
||||||
|
|
||||||
|
host: "$CUSTOM-DOMAIN";
|
||||||
|
base_path: "/";
|
||||||
|
|
||||||
|
external_docs: {
|
||||||
|
description: "Detailed information about ZITADEL",
|
||||||
|
url: "https://zitadel.com/docs"
|
||||||
|
}
|
||||||
|
security_definitions: {
|
||||||
|
security: {
|
||||||
|
key: "OAuth2";
|
||||||
|
value: {
|
||||||
|
type: TYPE_OAUTH2;
|
||||||
|
flow: FLOW_ACCESS_CODE;
|
||||||
|
authorization_url: "$CUSTOM-DOMAIN/oauth/v2/authorize";
|
||||||
|
token_url: "$CUSTOM-DOMAIN/oauth/v2/token";
|
||||||
|
scopes: {
|
||||||
|
scope: {
|
||||||
|
key: "openid";
|
||||||
|
value: "openid";
|
||||||
|
}
|
||||||
|
scope: {
|
||||||
|
key: "urn:zitadel:iam:org:project:id:zitadel:aud";
|
||||||
|
value: "urn:zitadel:iam:org:project:id:zitadel:aud";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
security: {
|
||||||
|
security_requirement: {
|
||||||
|
key: "OAuth2";
|
||||||
|
value: {
|
||||||
|
scope: "openid";
|
||||||
|
scope: "urn:zitadel:iam:org:project:id:zitadel:aud";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
responses: {
|
||||||
|
key: "403";
|
||||||
|
value: {
|
||||||
|
description: "Returned when the user does not have permission to access the resource.";
|
||||||
|
schema: {
|
||||||
|
json_schema: {
|
||||||
|
ref: "#/definitions/rpcStatus";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
responses: {
|
||||||
|
key: "404";
|
||||||
|
value: {
|
||||||
|
description: "Returned when the resource does not exist.";
|
||||||
|
schema: {
|
||||||
|
json_schema: {
|
||||||
|
ref: "#/definitions/rpcStatus";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Service to manage apps.
|
||||||
|
// The service provides methods to create, update, delete and list apps and app keys.
|
||||||
|
service AppService {
|
||||||
|
|
||||||
|
// Create Application
|
||||||
|
//
|
||||||
|
// 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) {
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "The created application";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/v2beta/applications"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "authenticated"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update Application
|
||||||
|
//
|
||||||
|
// 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) {
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "The updated app.";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
option (google.api.http) = {
|
||||||
|
patch: "/v2beta/applications/{id}"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "authenticated"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Application
|
||||||
|
//
|
||||||
|
// 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) {
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "The fetched app.";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
option (google.api.http) = {
|
||||||
|
get: "/v2beta/applications/{id}"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "authenticated"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
// Required permissions:
|
||||||
|
// - project.app.delete
|
||||||
|
rpc DeleteApplication(DeleteApplicationRequest) returns (DeleteApplicationResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
delete: "/v2beta/applications/{id}"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "The time of deletion.";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "authenticated"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
// Required permissions:
|
||||||
|
// - project.app.write
|
||||||
|
rpc DeactivateApplication(DeactivateApplicationRequest) returns (DeactivateApplicationResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/v2beta/applications/{id}/deactivate"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "The time of deactivation.";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "authenticated"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
//
|
||||||
|
// Required permissions:
|
||||||
|
// - project.app.write
|
||||||
|
rpc ReactivateApplication(ReactivateApplicationRequest) returns (ReactivateApplicationResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/v2beta/applications/{id}/reactivate"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "The time of reactivation.";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "authenticated"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Regenerate Client Secret
|
||||||
|
//
|
||||||
|
// 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) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/v2beta/applications/{application_id}/generate_client_secret"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
responses: {
|
||||||
|
key: "200";
|
||||||
|
value: {
|
||||||
|
description: "The regenerated client secret.";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.protoc_gen_zitadel.v2.options) = {
|
||||||
|
auth_option: {
|
||||||
|
permission: "authenticated"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// List Applications
|
||||||
|
//
|
||||||
|
// Returns a list of applications matching the input parameters that belong to the provided
|
||||||
|
// project.
|
||||||
|
//
|
||||||
|
// 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) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/v2beta/applications/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 {
|
||||||
|
string project_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||||
|
string id = 2 [(validate.rules).string = {max_len: 200}];
|
||||||
|
string name = 3 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||||
|
(google.api.field_behavior) = REQUIRED,
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 200;
|
||||||
|
example: "\"MyApp\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
oneof creation_request_type {
|
||||||
|
option (validate.required) = true;
|
||||||
|
CreateOIDCApplicationRequest oidc_request = 4;
|
||||||
|
CreateSAMLApplicationRequest saml_request = 5;
|
||||||
|
CreateAPIApplicationRequest api_request = 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message CreateApplicationResponse {
|
||||||
|
string app_id = 1;
|
||||||
|
// 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\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
oneof creation_response_type {
|
||||||
|
CreateOIDCApplicationResponse oidc_response = 3;
|
||||||
|
CreateSAMLApplicationResponse saml_response = 4;
|
||||||
|
CreateAPIApplicationResponse api_response = 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message CreateOIDCApplicationRequest {
|
||||||
|
// Callback URI of the authorization request where the code or tokens will be sent to
|
||||||
|
repeated string redirect_uris = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "[\"http://localhost:4200/auth/callback\"]";
|
||||||
|
description: "Callback URI of the authorization request where the code or tokens will be sent to";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
repeated OIDCResponseType response_types = 2 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Determines whether a code, id_token token or just id_token will be returned"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
repeated OIDCGrantType grant_types = 3 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "The flow type the application uses to gain access";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
OIDCAppType app_type = 4 [
|
||||||
|
(validate.rules).enum = {defined_only: true},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Determines the paradigm of the application";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
OIDCAuthMethodType auth_method_type = 5 [
|
||||||
|
(validate.rules).enum = {defined_only: true},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Defines how the application passes login credentials";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// ZITADEL will redirect to this link after a successful logout
|
||||||
|
repeated string post_logout_redirect_uris = 6 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "[\"http://localhost:4200/signedout\"]";
|
||||||
|
description: "ZITADEL will redirect to this link after a successful logout";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
OIDCVersion version = 7 [(validate.rules).enum = {defined_only: true}];
|
||||||
|
bool dev_mode = 8 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Used for development, some checks of the OIDC specification will not be checked.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
OIDCTokenType access_token_type = 9 [
|
||||||
|
(validate.rules).enum = {defined_only: true},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Type of the access token returned from ZITADEL";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
bool access_token_role_assertion = 10 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Adds roles to the claims of the access token (only if type == JWT) even if they are not requested by scopes";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
bool id_token_role_assertion = 11 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Adds roles to the claims of the id token even if they are not requested by scopes";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
bool id_token_userinfo_assertion = 12 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Claims of profile, email, address and phone scopes are added to the id token even if an access token is issued. Attention this violates the OIDC specification";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
google.protobuf.Duration clock_skew = 13 [
|
||||||
|
(validate.rules).duration = {gte: {}, lte: {seconds: 5}},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Used to compensate time difference of servers. Duration added to the \"exp\" claim and subtracted from \"iat\", \"auth_time\" and \"nbf\" claims";
|
||||||
|
example: "\"1s\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
repeated string additional_origins = 14 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "[\"scheme://localhost:8080\"]";
|
||||||
|
description: "Additional origins (other than the redirect_uris) from where the API can be used, provided string has to be an origin (scheme://hostname[:port]) without path, query or fragment";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
bool skip_native_app_success_page = 15 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Skip the successful login page on native apps and directly redirect the user to the callback.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
string back_channel_logout_uri = 16 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "[\"https://example.com/auth/backchannel\"]";
|
||||||
|
description: "ZITADEL will use this URI to notify the application about terminated session according to the OIDC Back-Channel Logout (https://openid.net/specs/openid-connect-backchannel-1_0.html)";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
LoginVersion login_version = 17 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Specify the preferred login UI, where the user is redirected to for authentication. If unset, the login UI is chosen by the instance default.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message CreateOIDCApplicationResponse {
|
||||||
|
string client_id = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"1035496534033449\"";
|
||||||
|
description: "generated client id for this config";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
string client_secret = 2 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"gjoq34589uasgh\"";
|
||||||
|
description: "generated secret for this config";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
bool none_compliant = 3;
|
||||||
|
repeated OIDCLocalizedMessage compliance_problems = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message CreateSAMLApplicationRequest {
|
||||||
|
oneof metadata {
|
||||||
|
option (validate.required) = true;
|
||||||
|
bytes metadata_xml = 1 [(validate.rules).bytes.max_len = 500000];
|
||||||
|
string metadata_url = 2 [(validate.rules).string.max_len = 200];
|
||||||
|
}
|
||||||
|
LoginVersion login_version = 3 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Specify the preferred login UI, where the user is redirected to for authentication. If unset, the login UI is chosen by the instance default.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message CreateSAMLApplicationResponse {}
|
||||||
|
|
||||||
|
message CreateAPIApplicationRequest {
|
||||||
|
APIAuthMethodType auth_method_type = 1 [(validate.rules).enum = {defined_only: true}];
|
||||||
|
}
|
||||||
|
|
||||||
|
message CreateAPIApplicationResponse {
|
||||||
|
string client_id = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"3950723409029374\"";
|
||||||
|
description: "generated secret for this config";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
string client_secret = 2 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"gjoq34589uasgh\"";
|
||||||
|
description: "generated secret for this config";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message UpdateApplicationRequest {
|
||||||
|
string project_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||||
|
string id = 2 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||||
|
(google.api.field_behavior) = REQUIRED,
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 200;
|
||||||
|
example: "\"45984352431\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
string name = 3 [
|
||||||
|
(validate.rules).string = {max_len: 200},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"MyApplicationName\"";
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 200;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
oneof update_request_type {
|
||||||
|
UpdateSAMLApplicationConfigurationRequest saml_configuration_request = 4;
|
||||||
|
UpdateOIDCApplicationConfigurationRequest oidc_configuration_request = 5;
|
||||||
|
UpdateAPIApplicationConfigurationRequest api_configuration_request = 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message UpdateApplicationResponse {
|
||||||
|
// The timestamp of the app update.
|
||||||
|
google.protobuf.Timestamp change_date = 2 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2024-12-18T07:50:47.492Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message UpdateSAMLApplicationConfigurationRequest {
|
||||||
|
oneof metadata {
|
||||||
|
option (validate.required) = true;
|
||||||
|
bytes metadata_xml = 1 [(validate.rules).bytes.max_len = 500000];
|
||||||
|
string metadata_url = 2 [(validate.rules).string.max_len = 200];
|
||||||
|
}
|
||||||
|
optional LoginVersion login_version = 3 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Specify the preferred login UI, where the user is redirected to for authentication. If unset, the login UI is chosen by the instance default.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message UpdateOIDCApplicationConfigurationRequest {
|
||||||
|
repeated string redirect_uris = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "[\"http://localhost:4200/auth/callback\"]";
|
||||||
|
description: "Callback URI of the authorization request where the code or tokens will be sent to";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
repeated OIDCResponseType response_types = 2 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Determines whether a code, id_token token or just id_token will be returned"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
repeated OIDCGrantType grant_types = 3 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "The flow type the application uses to gain access";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
optional OIDCAppType app_type = 4 [
|
||||||
|
(validate.rules).enum = {defined_only: true},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Determines the paradigm of the application";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
optional OIDCAuthMethodType auth_method_type = 5 [
|
||||||
|
(validate.rules).enum = {defined_only: true},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Defines how the application passes login credentials";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
repeated string post_logout_redirect_uris = 6 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "[\"http://localhost:4200/signedout\"]";
|
||||||
|
description: "ZITADEL will redirect to this link after a successful logout";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
optional OIDCVersion version = 7 [(validate.rules).enum = {defined_only: true}];
|
||||||
|
optional bool dev_mode = 8 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Used for development, some checks of the OIDC specification will not be checked.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
optional OIDCTokenType access_token_type = 9 [
|
||||||
|
(validate.rules).enum = {defined_only: true},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Type of the access token returned from ZITADEL";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
optional bool access_token_role_assertion = 10 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Adds roles to the claims of the access token (only if type == JWT) even if they are not requested by scopes";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
optional bool id_token_role_assertion = 11 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Adds roles to the claims of the id token even if they are not requested by scopes";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
optional bool id_token_userinfo_assertion = 12 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Claims of profile, email, address and phone scopes are added to the id token even if an access token is issued. Attention this violates the OIDC specification";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
optional google.protobuf.Duration clock_skew = 13 [
|
||||||
|
(validate.rules).duration = {gte: {}, lte: {seconds: 5}},
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Used to compensate time difference of servers. Duration added to the \"exp\" claim and subtracted from \"iat\", \"auth_time\" and \"nbf\" claims";
|
||||||
|
example: "\"1s\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
repeated string additional_origins = 14 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "[\"scheme://localhost:8080\"]";
|
||||||
|
description: "Additional origins (other than the redirect_uris) from where the API can be used, provided string has to be an origin (scheme://hostname[:port]) without path, query or fragment";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
optional bool skip_native_app_success_page = 15 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Skip the successful login page on native apps and directly redirect the user to the callback.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
optional string back_channel_logout_uri = 16 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "[\"https://example.com/auth/backchannel\"]";
|
||||||
|
description: "ZITADEL will use this URI to notify the application about terminated session according to the OIDC Back-Channel Logout (https://openid.net/specs/openid-connect-backchannel-1_0.html)";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
optional LoginVersion login_version = 17 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Specify the preferred login UI, where the user is redirected to for authentication. If unset, the login UI is chosen by the instance default.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message UpdateAPIApplicationConfigurationRequest {
|
||||||
|
APIAuthMethodType auth_method_type = 1 [(validate.rules).enum = {defined_only: true}];
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetApplicationRequest {
|
||||||
|
string id = 1 [
|
||||||
|
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||||
|
(google.api.field_behavior) = REQUIRED,
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
min_length: 1;
|
||||||
|
max_length: 200;
|
||||||
|
example: "\"45984352431\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetApplicationResponse {
|
||||||
|
Application app = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteApplicationRequest {
|
||||||
|
string project_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||||
|
string id = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeleteApplicationResponse {
|
||||||
|
google.protobuf.Timestamp deletion_date = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeactivateApplicationRequest{
|
||||||
|
string project_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||||
|
string id = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||||
|
}
|
||||||
|
|
||||||
|
message DeactivateApplicationResponse{
|
||||||
|
google.protobuf.Timestamp deactivation_date = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message ReactivateApplicationRequest{
|
||||||
|
string project_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||||
|
string id = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||||
|
}
|
||||||
|
|
||||||
|
message ReactivateApplicationResponse{
|
||||||
|
google.protobuf.Timestamp reactivation_date = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message RegenerateClientSecretRequest{
|
||||||
|
string project_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||||
|
string application_id = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||||
|
oneof app_type {
|
||||||
|
option (validate.required) = true;
|
||||||
|
bool is_oidc = 3;
|
||||||
|
bool is_api = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message RegenerateClientSecretResponse{
|
||||||
|
string client_secret = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"gjoq34589uasgh\"";
|
||||||
|
description: "generated secret for the client";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// The timestamp of the creation of the new client secret
|
||||||
|
google.protobuf.Timestamp creation_date = 2 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"2025-01-23T10:34:18.051Z\"";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListApplicationsRequest {
|
||||||
|
string project_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||||
|
|
||||||
|
// Pagination and sorting.
|
||||||
|
zitadel.filter.v2.PaginationRequest pagination = 2;
|
||||||
|
|
||||||
|
//criteria the client is looking for
|
||||||
|
repeated ApplicationSearchFilter filters = 3;
|
||||||
|
|
||||||
|
AppSorting sorting_column = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ListApplicationsResponse {
|
||||||
|
repeated Application applications = 1;
|
||||||
|
|
||||||
|
// Contains the total number of apps matching the query and the applied limit.
|
||||||
|
zitadel.filter.v2.PaginationResponse pagination = 2;
|
||||||
|
}
|
18
proto/zitadel/app/v2beta/login.proto
Normal file
18
proto/zitadel/app/v2beta/login.proto
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package zitadel.app.v2beta;
|
||||||
|
option go_package = "github.com/zitadel/zitadel/pkg/grpc/app/v2beta;app";
|
||||||
|
|
||||||
|
message LoginVersion {
|
||||||
|
oneof version {
|
||||||
|
LoginV1 login_v1 = 1;
|
||||||
|
LoginV2 login_v2 = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message LoginV1 {}
|
||||||
|
|
||||||
|
message LoginV2 {
|
||||||
|
// Optionally specify a base uri of the login UI. If unspecified the default URI will be used.
|
||||||
|
optional string base_uri = 1;
|
||||||
|
}
|
166
proto/zitadel/app/v2beta/oidc.proto
Normal file
166
proto/zitadel/app/v2beta/oidc.proto
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package zitadel.app.v2beta;
|
||||||
|
import "zitadel/app/v2beta/login.proto";
|
||||||
|
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||||
|
import "google/protobuf/duration.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/zitadel/zitadel/pkg/grpc/app/v2beta;app";
|
||||||
|
|
||||||
|
message OIDCLocalizedMessage {
|
||||||
|
string key = 1;
|
||||||
|
string localized_message = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OIDCResponseType {
|
||||||
|
OIDC_RESPONSE_TYPE_UNSPECIFIED = 0;
|
||||||
|
OIDC_RESPONSE_TYPE_CODE = 1;
|
||||||
|
OIDC_RESPONSE_TYPE_ID_TOKEN = 2;
|
||||||
|
OIDC_RESPONSE_TYPE_ID_TOKEN_TOKEN = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OIDCGrantType{
|
||||||
|
OIDC_GRANT_TYPE_AUTHORIZATION_CODE = 0;
|
||||||
|
OIDC_GRANT_TYPE_IMPLICIT = 1;
|
||||||
|
OIDC_GRANT_TYPE_REFRESH_TOKEN = 2;
|
||||||
|
OIDC_GRANT_TYPE_DEVICE_CODE = 3;
|
||||||
|
OIDC_GRANT_TYPE_TOKEN_EXCHANGE = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OIDCAppType {
|
||||||
|
OIDC_APP_TYPE_WEB = 0;
|
||||||
|
OIDC_APP_TYPE_USER_AGENT = 1;
|
||||||
|
OIDC_APP_TYPE_NATIVE = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OIDCAuthMethodType {
|
||||||
|
OIDC_AUTH_METHOD_TYPE_BASIC = 0;
|
||||||
|
OIDC_AUTH_METHOD_TYPE_POST = 1;
|
||||||
|
OIDC_AUTH_METHOD_TYPE_NONE = 2;
|
||||||
|
OIDC_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OIDCVersion {
|
||||||
|
OIDC_VERSION_1_0 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum OIDCTokenType {
|
||||||
|
OIDC_TOKEN_TYPE_BEARER = 0;
|
||||||
|
OIDC_TOKEN_TYPE_JWT = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message OIDCConfig {
|
||||||
|
repeated string redirect_uris = 1 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "[\"https://console.zitadel.ch/auth/callback\"]";
|
||||||
|
description: "Callback URI of the authorization request where the code or tokens will be sent to";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
repeated OIDCResponseType response_types = 2 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Determines whether a code, id_token token or just id_token will be returned"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
repeated OIDCGrantType grant_types = 3 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "The flow type the application uses to gain access";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
OIDCAppType app_type = 4 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "determines the paradigm of the application";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
string client_id = 5 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "\"69629023906488334@ZITADEL\"";
|
||||||
|
description: "generated oauth2/oidc client id";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
OIDCAuthMethodType auth_method_type = 6 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "defines how the application passes login credentials";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
repeated string post_logout_redirect_uris = 7 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "[\"https://console.zitadel.ch/logout\"]";
|
||||||
|
description: "ZITADEL will redirect to this link after a successful logout";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
OIDCVersion version = 8 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "the OIDC version used by the application";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
bool none_compliant = 9 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "specifies whether the config is OIDC compliant. A production configuration SHOULD be compliant";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
repeated OIDCLocalizedMessage compliance_problems = 10 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "lists the problems for non-compliancy";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
bool dev_mode = 11 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "used for development";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
OIDCTokenType access_token_type = 12 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "type of the access token returned from ZITADEL";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
bool access_token_role_assertion = 13 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "adds roles to the claims of the access token (only if type == JWT) even if they are not requested by scopes";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
bool id_token_role_assertion = 14 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "adds roles to the claims of the id token even if they are not requested by scopes";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
bool id_token_userinfo_assertion = 15 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "claims of profile, email, address and phone scopes are added to the id token even if an access token is issued. Attention this violates the OIDC specification";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
google.protobuf.Duration clock_skew = 16 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Used to compensate time difference of servers. Duration added to the \"exp\" claim and subtracted from \"iat\", \"auth_time\" and \"nbf\" claims";
|
||||||
|
// min: "0s";
|
||||||
|
// max: "5s";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
repeated string additional_origins = 17 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "[\"https://console.zitadel.ch/auth/callback\"]";
|
||||||
|
description: "additional origins (other than the redirect_uris) from where the API can be used";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
repeated string allowed_origins = 18 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "[\"https://console.zitadel.ch/auth/callback\"]";
|
||||||
|
description: "all allowed origins from where the API can be used";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
bool skip_native_app_success_page = 19 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Skip the successful login page on native apps and directly redirect the user to the callback.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
string back_channel_logout_uri = 20 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
example: "[\"https://example.com/auth/backchannel\"]";
|
||||||
|
description: "ZITADEL will use this URI to notify the application about terminated session according to the OIDC Back-Channel Logout (https://openid.net/specs/openid-connect-backchannel-1_0.html)";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
LoginVersion login_version = 21 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Specify the preferred login UI, where the user is redirected to for authentication. If unset, the login UI is chosen by the instance default.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
20
proto/zitadel/app/v2beta/saml.proto
Normal file
20
proto/zitadel/app/v2beta/saml.proto
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package zitadel.app.v2beta;
|
||||||
|
|
||||||
|
import "zitadel/app/v2beta/login.proto";
|
||||||
|
import "protoc-gen-openapiv2/options/annotations.proto";
|
||||||
|
|
||||||
|
option go_package = "github.com/zitadel/zitadel/pkg/grpc/app/v2beta;app";
|
||||||
|
|
||||||
|
message SAMLConfig {
|
||||||
|
oneof metadata{
|
||||||
|
bytes metadata_xml = 1;
|
||||||
|
string metadata_url = 2;
|
||||||
|
}
|
||||||
|
LoginVersion login_version = 3 [
|
||||||
|
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||||
|
description: "Specify the preferred login UI, where the user is redirected to for authentication. If unset, the login UI is chosen by the instance default.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
@@ -3287,6 +3287,7 @@ service ManagementService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use [GetApplication](/apis/resources/application_service_v2/application-service-get-application.api.mdx) instead to fetch an app
|
||||||
rpc GetAppByID(GetAppByIDRequest) returns (GetAppByIDResponse) {
|
rpc GetAppByID(GetAppByIDRequest) returns (GetAppByIDResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
get: "/projects/{project_id}/apps/{app_id}"
|
get: "/projects/{project_id}/apps/{app_id}"
|
||||||
@@ -3309,9 +3310,11 @@ service ManagementService {
|
|||||||
required: false;
|
required: false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
deprecated: true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use [ListApplications](/apis/resources/application_service_v2/application-service-list-applications.api.mdx) instead to list applications
|
||||||
rpc ListApps(ListAppsRequest) returns (ListAppsResponse) {
|
rpc ListApps(ListAppsRequest) returns (ListAppsResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
post: "/projects/{project_id}/apps/_search"
|
post: "/projects/{project_id}/apps/_search"
|
||||||
@@ -3335,6 +3338,7 @@ service ManagementService {
|
|||||||
required: false;
|
required: false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
deprecated: true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3363,6 +3367,7 @@ service ManagementService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use [CreateApplication](/apis/resources/application_service_v2/application-service-create-application.api.mdx) instead to create an OIDC application
|
||||||
rpc AddOIDCApp(AddOIDCAppRequest) returns (AddOIDCAppResponse) {
|
rpc AddOIDCApp(AddOIDCAppRequest) returns (AddOIDCAppResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
post: "/projects/{project_id}/apps/oidc"
|
post: "/projects/{project_id}/apps/oidc"
|
||||||
@@ -3386,62 +3391,74 @@ service ManagementService {
|
|||||||
required: false;
|
required: false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
deprecated: true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
rpc AddSAMLApp(AddSAMLAppRequest) returns (AddSAMLAppResponse) {
|
// Deprecated: Use [CreateApplication](/apis/resources/application_service_v2/application-service-create-application.api.mdx) instead to create a SAML application
|
||||||
option (google.api.http) = {
|
rpc AddSAMLApp(AddSAMLAppRequest) returns (AddSAMLAppResponse) {
|
||||||
post: "/projects/{project_id}/apps/saml"
|
option (google.api.http) = {
|
||||||
body: "*"
|
post: "/projects/{project_id}/apps/saml"
|
||||||
};
|
body: "*"
|
||||||
|
|
||||||
option (zitadel.v1.auth_option) = {
|
|
||||||
permission: "project.app.write"
|
|
||||||
check_field_name: "ProjectId"
|
|
||||||
};
|
|
||||||
|
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
|
||||||
tags: "Applications";
|
|
||||||
summary: "Create Application (SAML)";
|
|
||||||
description: "Create a new SAML client. Returns an entity ID"
|
|
||||||
parameters: {
|
|
||||||
headers: {
|
|
||||||
name: "x-zitadel-orgid";
|
|
||||||
description: "The default is always the organization of the requesting user. If you like to change/get objects of another organization include the header. Make sure the requesting user has permission to access the requested data.";
|
|
||||||
type: STRING,
|
|
||||||
required: false;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
rpc AddAPIApp(AddAPIAppRequest) returns (AddAPIAppResponse) {
|
|
||||||
option (google.api.http) = {
|
|
||||||
post: "/projects/{project_id}/apps/api"
|
|
||||||
body: "*"
|
|
||||||
};
|
|
||||||
|
|
||||||
option (zitadel.v1.auth_option) = {
|
|
||||||
permission: "project.app.write"
|
|
||||||
check_field_name: "ProjectId"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
option (zitadel.v1.auth_option) = {
|
||||||
tags: "Applications";
|
permission: "project.app.write"
|
||||||
summary: "Create Application (API)";
|
check_field_name: "ProjectId"
|
||||||
description: "Create a new API client. The client id will be generated and returned in the response. Depending on the chosen configuration also a secret will be generated and returned."
|
};
|
||||||
parameters: {
|
|
||||||
headers: {
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
name: "x-zitadel-orgid";
|
tags: "Applications";
|
||||||
description: "The default is always the organization of the requesting user. If you like to change/get objects of another organization include the header. Make sure the requesting user has permission to access the requested data.";
|
summary: "Create Application (SAML)";
|
||||||
type: STRING,
|
description: "Create a new SAML client. Returns an entity ID"
|
||||||
required: false;
|
parameters: {
|
||||||
};
|
headers: {
|
||||||
};
|
name: "x-zitadel-orgid";
|
||||||
};
|
description: "The default is always the organization of the requesting user. If you like to change/get objects of another organization include the header. Make sure the requesting user has permission to access the requested data.";
|
||||||
|
type: STRING,
|
||||||
|
required: false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
deprecated: true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Application (API)
|
||||||
|
//
|
||||||
|
// Create a new API client. The client id will be generated and returned in the response.
|
||||||
|
// Depending on the chosen configuration also a secret will be generated and returned.
|
||||||
|
//
|
||||||
|
// Deprecated: Use [CreateApplication](/apis/resources/application_service_v2/application-service-create-application.api.mdx) instead to create an API application
|
||||||
|
rpc AddAPIApp(AddAPIAppRequest) returns (AddAPIAppResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
post: "/projects/{project_id}/apps/api"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.v1.auth_option) = {
|
||||||
|
permission: "project.app.write"
|
||||||
|
check_field_name: "ProjectId"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
tags: "Applications";
|
||||||
|
summary: "Create Application (API)";
|
||||||
|
description: "Create a new API client. The client id will be generated and returned in the response. Depending on the chosen configuration also a secret will be generated and returned."
|
||||||
|
parameters: {
|
||||||
|
headers: {
|
||||||
|
name: "x-zitadel-orgid";
|
||||||
|
description: "The default is always the organization of the requesting user. If you like to change/get objects of another organization include the header. Make sure the requesting user has permission to access the requested data.";
|
||||||
|
type: STRING,
|
||||||
|
required: false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
deprecated: true;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Changes application
|
// Changes application
|
||||||
|
//
|
||||||
|
// Deprecated: Use [PatchApplication](/apis/resources/application_service_v2/application-service-patch-application.api.mdx) instead to update the generic params of an app
|
||||||
rpc UpdateApp(UpdateAppRequest) returns (UpdateAppResponse) {
|
rpc UpdateApp(UpdateAppRequest) returns (UpdateAppResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
put: "/projects/{project_id}/apps/{app_id}"
|
put: "/projects/{project_id}/apps/{app_id}"
|
||||||
@@ -3465,9 +3482,11 @@ service ManagementService {
|
|||||||
required: false;
|
required: false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
deprecated: true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use [PatchApplication](/apis/resources/application_service_v2/application-service-patch-application.api.mdx) instead to update the config of an OIDC app
|
||||||
rpc UpdateOIDCAppConfig(UpdateOIDCAppConfigRequest) returns (UpdateOIDCAppConfigResponse) {
|
rpc UpdateOIDCAppConfig(UpdateOIDCAppConfigRequest) returns (UpdateOIDCAppConfigResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
put: "/projects/{project_id}/apps/{app_id}/oidc_config"
|
put: "/projects/{project_id}/apps/{app_id}/oidc_config"
|
||||||
@@ -3491,61 +3510,67 @@ service ManagementService {
|
|||||||
required: false;
|
required: false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
deprecated: true
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
rpc UpdateSAMLAppConfig(UpdateSAMLAppConfigRequest) returns (UpdateSAMLAppConfigResponse) {
|
// Deprecated: Use [PatchApplication](/apis/resources/application_service_v2/application-service-patch-application.api.mdx) instead to update the config of a SAML app
|
||||||
option (google.api.http) = {
|
rpc UpdateSAMLAppConfig(UpdateSAMLAppConfigRequest) returns (UpdateSAMLAppConfigResponse) {
|
||||||
put: "/projects/{project_id}/apps/{app_id}/saml_config"
|
option (google.api.http) = {
|
||||||
body: "*"
|
put: "/projects/{project_id}/apps/{app_id}/saml_config"
|
||||||
};
|
body: "*"
|
||||||
|
};
|
||||||
option (zitadel.v1.auth_option) = {
|
|
||||||
permission: "project.app.write"
|
|
||||||
check_field_name: "ProjectId"
|
|
||||||
};
|
|
||||||
|
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
|
||||||
tags: "Applications";
|
|
||||||
summary: "Update SAML Application Config";
|
|
||||||
description: "Update the SAML specific configuration of an application."
|
|
||||||
parameters: {
|
|
||||||
headers: {
|
|
||||||
name: "x-zitadel-orgid";
|
|
||||||
description: "The default is always the organization of the requesting user. If you like to change/get objects of another organization include the header. Make sure the requesting user has permission to access the requested data.";
|
|
||||||
type: STRING,
|
|
||||||
required: false;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
rpc UpdateAPIAppConfig(UpdateAPIAppConfigRequest) returns (UpdateAPIAppConfigResponse) {
|
|
||||||
option (google.api.http) = {
|
|
||||||
put: "/projects/{project_id}/apps/{app_id}/api_config"
|
|
||||||
body: "*"
|
|
||||||
};
|
|
||||||
|
|
||||||
option (zitadel.v1.auth_option) = {
|
option (zitadel.v1.auth_option) = {
|
||||||
permission: "project.app.write"
|
permission: "project.app.write"
|
||||||
check_field_name: "ProjectId"
|
check_field_name: "ProjectId"
|
||||||
};
|
};
|
||||||
|
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
tags: "Applications";
|
tags: "Applications";
|
||||||
summary: "Update API Application Config";
|
summary: "Update SAML Application Config";
|
||||||
description: "Update the OIDC-specific configuration of an application."
|
description: "Update the SAML specific configuration of an application."
|
||||||
parameters: {
|
parameters: {
|
||||||
headers: {
|
headers: {
|
||||||
name: "x-zitadel-orgid";
|
name: "x-zitadel-orgid";
|
||||||
description: "The default is always the organization of the requesting user. If you like to change/get objects of another organization include the header. Make sure the requesting user has permission to access the requested data.";
|
description: "The default is always the organization of the requesting user. If you like to change/get objects of another organization include the header. Make sure the requesting user has permission to access the requested data.";
|
||||||
type: STRING,
|
type: STRING,
|
||||||
required: false;
|
required: false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
deprecated: true;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use [PatchApplication](/apis/resources/application_service_v2/application-service-patch-application.api.mdx) instead to update the config of an API app
|
||||||
|
rpc UpdateAPIAppConfig(UpdateAPIAppConfigRequest) returns (UpdateAPIAppConfigResponse) {
|
||||||
|
option (google.api.http) = {
|
||||||
|
put: "/projects/{project_id}/apps/{app_id}/api_config"
|
||||||
|
body: "*"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (zitadel.v1.auth_option) = {
|
||||||
|
permission: "project.app.write"
|
||||||
|
check_field_name: "ProjectId"
|
||||||
|
};
|
||||||
|
|
||||||
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||||
|
tags: "Applications";
|
||||||
|
summary: "Update API Application Config";
|
||||||
|
description: "Update the OIDC-specific configuration of an application."
|
||||||
|
parameters: {
|
||||||
|
headers: {
|
||||||
|
name: "x-zitadel-orgid";
|
||||||
|
description: "The default is always the organization of the requesting user. If you like to change/get objects of another organization include the header. Make sure the requesting user has permission to access the requested data.";
|
||||||
|
type: STRING,
|
||||||
|
required: false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
deprecated: true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use [DeactivateApplication](/apis/resources/application_service_v2/application-service-deactivate-application.api.mdx) instead to deactivate an app
|
||||||
rpc DeactivateApp(DeactivateAppRequest) returns (DeactivateAppResponse) {
|
rpc DeactivateApp(DeactivateAppRequest) returns (DeactivateAppResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
post: "/projects/{project_id}/apps/{app_id}/_deactivate"
|
post: "/projects/{project_id}/apps/{app_id}/_deactivate"
|
||||||
@@ -3569,9 +3594,11 @@ service ManagementService {
|
|||||||
required: false;
|
required: false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
deprecated: true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use [ReactivateApplication](/apis/resources/application_service_v2/application-service-reactivate-application.api.mdx) instead to reactivate an app
|
||||||
rpc ReactivateApp(ReactivateAppRequest) returns (ReactivateAppResponse) {
|
rpc ReactivateApp(ReactivateAppRequest) returns (ReactivateAppResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
post: "/projects/{project_id}/apps/{app_id}/_reactivate"
|
post: "/projects/{project_id}/apps/{app_id}/_reactivate"
|
||||||
@@ -3595,9 +3622,11 @@ service ManagementService {
|
|||||||
required: false;
|
required: false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
deprecated: true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use [DeleteApplication](/apis/resources/application_service_v2/application-service-delete-application.api.mdx) instead to delete an app
|
||||||
rpc RemoveApp(RemoveAppRequest) returns (RemoveAppResponse) {
|
rpc RemoveApp(RemoveAppRequest) returns (RemoveAppResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
delete: "/projects/{project_id}/apps/{app_id}"
|
delete: "/projects/{project_id}/apps/{app_id}"
|
||||||
@@ -3620,9 +3649,11 @@ service ManagementService {
|
|||||||
required: false;
|
required: false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
deprecated: true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use [RegenerateClientSecret](/apis/resources/application_service_v2/application-service-regenerate-client-secret.api.mdx) instead to regenerate an OIDC app client secret
|
||||||
rpc RegenerateOIDCClientSecret(RegenerateOIDCClientSecretRequest) returns (RegenerateOIDCClientSecretResponse) {
|
rpc RegenerateOIDCClientSecret(RegenerateOIDCClientSecretRequest) returns (RegenerateOIDCClientSecretResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
post: "/projects/{project_id}/apps/{app_id}/oidc_config/_generate_client_secret"
|
post: "/projects/{project_id}/apps/{app_id}/oidc_config/_generate_client_secret"
|
||||||
@@ -3646,9 +3677,11 @@ service ManagementService {
|
|||||||
required: false;
|
required: false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
deprecated: true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use [RegenerateClientSecret](/apis/resources/application_service_v2/application-service-regenerate-client-secret.api.mdx) instead to regenerate an API app client secret
|
||||||
rpc RegenerateAPIClientSecret(RegenerateAPIClientSecretRequest) returns (RegenerateAPIClientSecretResponse) {
|
rpc RegenerateAPIClientSecret(RegenerateAPIClientSecretRequest) returns (RegenerateAPIClientSecretResponse) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
post: "/projects/{project_id}/apps/{app_id}/api_config/_generate_client_secret"
|
post: "/projects/{project_id}/apps/{app_id}/api_config/_generate_client_secret"
|
||||||
@@ -3672,6 +3705,7 @@ service ManagementService {
|
|||||||
required: false;
|
required: false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
deprecated: true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user