mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-11 16:45:13 +00:00
feat(api): move application service v2beta to GA (and deprecate v2beta) (#10846)
# Which Problems Are Solved As part of our efforts to simplify the structure and versions of our APIs, were moving all existing v2beta endpoints to v2 and deprecate them. They will be removed in Zitadel V5. # How the Problems Are Solved - This PR moves app v2beta service and its endpoints to a corresponding to application v2 version. The v2beta service and endpoints are deprecated. - The comments and have been improved and, where not already done, moved from swagger annotations to proto. - All required fields have been marked with (google.api.field_behavior) = REQUIRED and validation rules have been added where missing. - Name ID of the application always `application_id`, previously was also `id` and `app_id`. - Get rid of all `app` abbreviations and name it `application` including the service name, `AppState` -> `ApplicationState` and `AppSorting` -> `ApplicationSorting` - Updated `CreateApplicationRequest`: - renamed `creation_request_type` to `application_type` and all its options to `XY_configuration` instead of `XY_request` - `RegenerateClientSecret` - renamed method to `GenerateClientSecret` - removed `app_type` from request - `ListApplicationRequest`: - removed required `project_id` and provided it as a filter - Type `ApplicationNameQuery` has been renamed to `ApplicationNameFilter` as its usage in the request - Renamed all fields and types from `config` to `configuration` - Updated `DeleteApplicationKeyRequest` - removed `organization_id` - Updated `GetApplicationKeyRequest`: - removed `project_id`, `application_id` and `organization_id`` - Updated `ListApplicationKeysRequest`: - removed oneOf `resource_id` and moved the options into filters - Name ID of the application key always `key_id`. - removed unnecessary package prefixed (`zitadel.application.v2`) - formatted using `buf` # Additional Changes None # Additional Context - part of https://github.com/zitadel/zitadel/issues/10772 - requires backport to v4.x
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/application/v2/convert"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/application/v2"
|
||||
)
|
||||
|
||||
func (s *Server) CreateApplicationKey(ctx context.Context, req *connect.Request[application.CreateApplicationKeyRequest]) (*connect.Response[application.CreateApplicationKeyResponse], error) {
|
||||
domainReq := convert.CreateAPIClientKeyRequestToDomain(req.Msg)
|
||||
|
||||
appKey, err := s.command.AddApplicationKey(ctx, domainReq, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keyDetails, err := appKey.Detail()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return connect.NewResponse(&application.CreateApplicationKeyResponse{
|
||||
KeyId: appKey.KeyID,
|
||||
CreationDate: timestamppb.New(appKey.ChangeDate),
|
||||
KeyDetails: keyDetails,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) DeleteApplicationKey(ctx context.Context, req *connect.Request[application.DeleteApplicationKeyRequest]) (*connect.Response[application.DeleteApplicationKeyResponse], error) {
|
||||
deletionDetails, err := s.command.RemoveApplicationKey(ctx,
|
||||
strings.TrimSpace(req.Msg.GetProjectId()),
|
||||
strings.TrimSpace(req.Msg.GetApplicationId()),
|
||||
strings.TrimSpace(req.Msg.GetKeyId()),
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return connect.NewResponse(&application.DeleteApplicationKeyResponse{
|
||||
DeletionDate: timestamppb.New(deletionDetails.EventDate),
|
||||
}), nil
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/application/v2/convert"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/application/v2"
|
||||
)
|
||||
|
||||
func (s *Server) CreateApplication(ctx context.Context, req *connect.Request[application.CreateApplicationRequest]) (*connect.Response[application.CreateApplicationResponse], error) {
|
||||
switch t := req.Msg.GetApplicationType().(type) {
|
||||
case *application.CreateApplicationRequest_ApiConfiguration:
|
||||
apiApp, err := s.command.AddAPIApplication(ctx, convert.CreateAPIApplicationRequestToDomain(req.Msg.GetName(), req.Msg.GetProjectId(), req.Msg.GetApplicationId(), t.ApiConfiguration), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return connect.NewResponse(&application.CreateApplicationResponse{
|
||||
ApplicationId: apiApp.AppID,
|
||||
CreationDate: timestamppb.New(apiApp.ChangeDate),
|
||||
ApplicationType: &application.CreateApplicationResponse_ApiConfiguration{
|
||||
ApiConfiguration: &application.CreateAPIApplicationResponse{
|
||||
ClientId: apiApp.ClientID,
|
||||
ClientSecret: apiApp.ClientSecretString,
|
||||
},
|
||||
},
|
||||
}), nil
|
||||
|
||||
case *application.CreateApplicationRequest_OidcConfiguration:
|
||||
oidcAppRequest, err := convert.CreateOIDCAppRequestToDomain(req.Msg.GetName(), req.Msg.GetProjectId(), req.Msg.GetOidcConfiguration())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
oidcApp, err := s.command.AddOIDCApplication(ctx, oidcAppRequest, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return connect.NewResponse(&application.CreateApplicationResponse{
|
||||
ApplicationId: oidcApp.AppID,
|
||||
CreationDate: timestamppb.New(oidcApp.ChangeDate),
|
||||
ApplicationType: &application.CreateApplicationResponse_OidcConfiguration{
|
||||
OidcConfiguration: &application.CreateOIDCApplicationResponse{
|
||||
ClientId: oidcApp.ClientID,
|
||||
ClientSecret: oidcApp.ClientSecretString,
|
||||
NonCompliant: oidcApp.Compliance.NoneCompliant,
|
||||
ComplianceProblems: convert.ComplianceProblemsToLocalizedMessages(oidcApp.Compliance.Problems),
|
||||
},
|
||||
},
|
||||
}), nil
|
||||
|
||||
case *application.CreateApplicationRequest_SamlConfiguration:
|
||||
samlAppRequest, err := convert.CreateSAMLAppRequestToDomain(req.Msg.GetName(), req.Msg.GetProjectId(), req.Msg.GetSamlConfiguration())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
samlApp, err := s.command.AddSAMLApplication(ctx, samlAppRequest, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return connect.NewResponse(&application.CreateApplicationResponse{
|
||||
ApplicationId: samlApp.AppID,
|
||||
CreationDate: timestamppb.New(samlApp.ChangeDate),
|
||||
ApplicationType: &application.CreateApplicationResponse_SamlConfiguration{
|
||||
SamlConfiguration: &application.CreateSAMLApplicationResponse{},
|
||||
},
|
||||
}), nil
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "APP-0iiN46", "unknown app type")
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) UpdateApplication(ctx context.Context, req *connect.Request[application.UpdateApplicationRequest]) (*connect.Response[application.UpdateApplicationResponse], error) {
|
||||
var changedTime time.Time
|
||||
|
||||
if name := strings.TrimSpace(req.Msg.GetName()); name != "" {
|
||||
updatedDetails, err := s.command.UpdateApplicationName(
|
||||
ctx,
|
||||
req.Msg.GetProjectId(),
|
||||
&domain.ChangeApp{
|
||||
AppID: req.Msg.GetApplicationId(),
|
||||
AppName: name,
|
||||
},
|
||||
"",
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
changedTime = updatedDetails.EventDate
|
||||
}
|
||||
|
||||
switch t := req.Msg.GetApplicationType().(type) {
|
||||
case *application.UpdateApplicationRequest_ApiConfiguration:
|
||||
updatedAPIApp, err := s.command.UpdateAPIApplication(ctx, convert.UpdateAPIApplicationConfigurationRequestToDomain(req.Msg.GetApplicationId(), req.Msg.GetProjectId(), t.ApiConfiguration), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
changedTime = updatedAPIApp.ChangeDate
|
||||
|
||||
case *application.UpdateApplicationRequest_OidcConfiguration:
|
||||
oidcApp, err := convert.UpdateOIDCAppConfigRequestToDomain(req.Msg.GetApplicationId(), req.Msg.GetProjectId(), t.OidcConfiguration)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updatedOIDCApp, err := s.command.UpdateOIDCApplication(ctx, oidcApp, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
changedTime = updatedOIDCApp.ChangeDate
|
||||
|
||||
case *application.UpdateApplicationRequest_SamlConfiguration:
|
||||
samlApp, err := convert.UpdateSAMLAppConfigRequestToDomain(req.Msg.GetApplicationId(), req.Msg.GetProjectId(), t.SamlConfiguration)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updatedSAMLApp, err := s.command.UpdateSAMLApplication(ctx, samlApp, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
changedTime = updatedSAMLApp.ChangeDate
|
||||
}
|
||||
|
||||
return connect.NewResponse(&application.UpdateApplicationResponse{
|
||||
ChangeDate: timestamppb.New(changedTime),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) DeleteApplication(ctx context.Context, req *connect.Request[application.DeleteApplicationRequest]) (*connect.Response[application.DeleteApplicationResponse], error) {
|
||||
details, err := s.command.RemoveApplication(ctx, req.Msg.GetProjectId(), req.Msg.GetApplicationId(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return connect.NewResponse(&application.DeleteApplicationResponse{
|
||||
DeletionDate: timestamppb.New(details.EventDate),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) DeactivateApplication(ctx context.Context, req *connect.Request[application.DeactivateApplicationRequest]) (*connect.Response[application.DeactivateApplicationResponse], error) {
|
||||
details, err := s.command.DeactivateApplication(ctx, req.Msg.GetProjectId(), req.Msg.GetApplicationId(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return connect.NewResponse(&application.DeactivateApplicationResponse{
|
||||
DeactivationDate: timestamppb.New(details.EventDate),
|
||||
}), nil
|
||||
|
||||
}
|
||||
|
||||
func (s *Server) ReactivateApplication(ctx context.Context, req *connect.Request[application.ReactivateApplicationRequest]) (*connect.Response[application.ReactivateApplicationResponse], error) {
|
||||
details, err := s.command.ReactivateApplication(ctx, req.Msg.GetProjectId(), req.Msg.GetApplicationId(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return connect.NewResponse(&application.ReactivateApplicationResponse{
|
||||
ReactivationDate: timestamppb.New(details.EventDate),
|
||||
}), nil
|
||||
|
||||
}
|
||||
|
||||
func (s *Server) GenerateClientSecret(ctx context.Context, req *connect.Request[application.GenerateClientSecretRequest]) (*connect.Response[application.GenerateClientSecretResponse], error) {
|
||||
var secret string
|
||||
var changeDate time.Time
|
||||
|
||||
secret, changeDate, err := s.command.ChangeApplicationSecret(ctx, req.Msg.GetProjectId(), req.Msg.GetApplicationId(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&application.GenerateClientSecretResponse{
|
||||
ClientSecret: secret,
|
||||
CreationDate: timestamppb.New(changeDate),
|
||||
}), nil
|
||||
}
|
||||
@@ -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"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/application/v2"
|
||||
)
|
||||
|
||||
func CreateAPIApplicationRequestToDomain(name, projectID, appID string, app *application.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 *application.UpdateAPIApplicationConfigurationRequest) *domain.APIApp {
|
||||
return &domain.APIApp{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: projectID,
|
||||
},
|
||||
AppID: appID,
|
||||
AuthMethodType: apiAuthMethodTypeToDomain(app.GetAuthMethodType()),
|
||||
}
|
||||
}
|
||||
|
||||
func appAPIConfigToPb(apiApp *query.APIApp) application.IsApplicationConfiguration {
|
||||
return &application.Application_ApiConfiguration{
|
||||
ApiConfiguration: &application.APIConfiguration{
|
||||
ClientId: apiApp.ClientID,
|
||||
AuthMethodType: apiAuthMethodTypeToPb(apiApp.AuthMethodType),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func apiAuthMethodTypeToDomain(authType application.APIAuthMethodType) domain.APIAuthMethodType {
|
||||
switch authType {
|
||||
case application.APIAuthMethodType_API_AUTH_METHOD_TYPE_BASIC:
|
||||
return domain.APIAuthMethodTypeBasic
|
||||
case application.APIAuthMethodType_API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT:
|
||||
return domain.APIAuthMethodTypePrivateKeyJWT
|
||||
default:
|
||||
return domain.APIAuthMethodTypeBasic
|
||||
}
|
||||
}
|
||||
|
||||
func apiAuthMethodTypeToPb(methodType domain.APIAuthMethodType) application.APIAuthMethodType {
|
||||
switch methodType {
|
||||
case domain.APIAuthMethodTypeBasic:
|
||||
return application.APIAuthMethodType_API_AUTH_METHOD_TYPE_BASIC
|
||||
case domain.APIAuthMethodTypePrivateKeyJWT:
|
||||
return application.APIAuthMethodType_API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT
|
||||
default:
|
||||
return application.APIAuthMethodType_API_AUTH_METHOD_TYPE_BASIC
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/application/v2"
|
||||
)
|
||||
|
||||
func TestCreateAPIApplicationRequestToDomain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
appName string
|
||||
projectID string
|
||||
appID string
|
||||
req *application.CreateAPIApplicationRequest
|
||||
want *domain.APIApp
|
||||
}{
|
||||
{
|
||||
name: "basic auth method",
|
||||
appName: "my-application",
|
||||
projectID: "proj-1",
|
||||
appID: "someID",
|
||||
req: &application.CreateAPIApplicationRequest{
|
||||
AuthMethodType: application.APIAuthMethodType_API_AUTH_METHOD_TYPE_BASIC,
|
||||
},
|
||||
want: &domain.APIApp{
|
||||
ObjectRoot: models.ObjectRoot{AggregateID: "proj-1"},
|
||||
AppName: "my-application",
|
||||
AuthMethodType: domain.APIAuthMethodTypeBasic,
|
||||
AppID: "someID",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "private key jwt",
|
||||
appName: "jwt-application",
|
||||
projectID: "proj-2",
|
||||
req: &application.CreateAPIApplicationRequest{
|
||||
AuthMethodType: application.APIAuthMethodType_API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT,
|
||||
},
|
||||
want: &domain.APIApp{
|
||||
ObjectRoot: models.ObjectRoot{AggregateID: "proj-2"},
|
||||
AppName: "jwt-application",
|
||||
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 *application.UpdateAPIApplicationConfigurationRequest
|
||||
want *domain.APIApp
|
||||
}{
|
||||
{
|
||||
name: "basic auth method",
|
||||
appID: "application-1",
|
||||
projectID: "proj-1",
|
||||
req: &application.UpdateAPIApplicationConfigurationRequest{
|
||||
AuthMethodType: application.APIAuthMethodType_API_AUTH_METHOD_TYPE_BASIC,
|
||||
},
|
||||
want: &domain.APIApp{
|
||||
ObjectRoot: models.ObjectRoot{AggregateID: "proj-1"},
|
||||
AppID: "application-1",
|
||||
AuthMethodType: domain.APIAuthMethodTypeBasic,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "private key jwt",
|
||||
appID: "application-2",
|
||||
projectID: "proj-2",
|
||||
req: &application.UpdateAPIApplicationConfigurationRequest{
|
||||
AuthMethodType: application.APIAuthMethodType_API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT,
|
||||
},
|
||||
want: &domain.APIApp{
|
||||
ObjectRoot: models.ObjectRoot{AggregateID: "proj-2"},
|
||||
AppID: "application-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 application.APIAuthMethodType
|
||||
}{
|
||||
{
|
||||
name: "basic auth method",
|
||||
methodType: domain.APIAuthMethodTypeBasic,
|
||||
expectedResult: application.APIAuthMethodType_API_AUTH_METHOD_TYPE_BASIC,
|
||||
},
|
||||
{
|
||||
name: "private key jwt",
|
||||
methodType: domain.APIAuthMethodTypePrivateKeyJWT,
|
||||
expectedResult: application.APIAuthMethodType_API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT,
|
||||
},
|
||||
{
|
||||
name: "unknown auth method defaults to basic",
|
||||
expectedResult: application.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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/filter/v2"
|
||||
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/application/v2"
|
||||
)
|
||||
|
||||
func AppToPb(queryApp *query.App) *application.Application {
|
||||
if queryApp == nil {
|
||||
return &application.Application{}
|
||||
}
|
||||
|
||||
return &application.Application{
|
||||
ApplicationId: queryApp.ID,
|
||||
CreationDate: timestamppb.New(queryApp.CreationDate),
|
||||
ChangeDate: timestamppb.New(queryApp.ChangeDate),
|
||||
State: appStateToPb(queryApp.State),
|
||||
Name: queryApp.Name,
|
||||
Configuration: appConfigToPb(queryApp),
|
||||
}
|
||||
}
|
||||
|
||||
func AppsToPb(queryApps []*query.App) []*application.Application {
|
||||
pbApps := make([]*application.Application, len(queryApps))
|
||||
|
||||
for i, queryApp := range queryApps {
|
||||
pbApps[i] = AppToPb(queryApp)
|
||||
}
|
||||
|
||||
return pbApps
|
||||
}
|
||||
|
||||
func ListApplicationsRequestToModel(sysDefaults systemdefaults.SystemDefaults, req *application.ListApplicationsRequest) (*query.AppSearchQueries, error) {
|
||||
offset, limit, asc, err := filter.PaginationPbToQuery(sysDefaults, req.GetPagination())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
queries, err := appQueriesToModel(req.GetFilters())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &query.AppSearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
SortingColumn: appSortingToColumn(req.GetSortingColumn()),
|
||||
},
|
||||
|
||||
Queries: queries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func appSortingToColumn(sortingCriteria application.ApplicationSorting) query.Column {
|
||||
switch sortingCriteria {
|
||||
case application.ApplicationSorting_APPLICATION_SORT_BY_CHANGE_DATE:
|
||||
return query.AppColumnChangeDate
|
||||
case application.ApplicationSorting_APPLICATION_SORT_BY_CREATION_DATE:
|
||||
return query.AppColumnCreationDate
|
||||
case application.ApplicationSorting_APPLICATION_SORT_BY_NAME:
|
||||
return query.AppColumnName
|
||||
case application.ApplicationSorting_APPLICATION_SORT_BY_STATE:
|
||||
return query.AppColumnState
|
||||
case application.ApplicationSorting_APPLICATION_SORT_BY_ID:
|
||||
fallthrough
|
||||
default:
|
||||
return query.AppColumnID
|
||||
}
|
||||
}
|
||||
|
||||
func appStateToPb(state domain.AppState) application.ApplicationState {
|
||||
switch state {
|
||||
case domain.AppStateActive:
|
||||
return application.ApplicationState_APPLICATION_STATE_ACTIVE
|
||||
case domain.AppStateInactive:
|
||||
return application.ApplicationState_APPLICATION_STATE_INACTIVE
|
||||
case domain.AppStateRemoved:
|
||||
return application.ApplicationState_APPLICATION_STATE_REMOVED
|
||||
case domain.AppStateUnspecified:
|
||||
fallthrough
|
||||
default:
|
||||
return application.ApplicationState_APPLICATION_STATE_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func appConfigToPb(app *query.App) application.IsApplicationConfiguration {
|
||||
if app.OIDCConfig != nil {
|
||||
return appOIDCConfigToPb(app.OIDCConfig)
|
||||
}
|
||||
if app.SAMLConfig != nil {
|
||||
return appSAMLConfigToPb(app.SAMLConfig)
|
||||
}
|
||||
return appAPIConfigToPb(app.APIConfig)
|
||||
}
|
||||
|
||||
func loginVersionToDomain(version *application.LoginVersion) (*domain.LoginVersion, *string, error) {
|
||||
switch v := version.GetVersion().(type) {
|
||||
case nil:
|
||||
return gu.Ptr(domain.LoginVersionUnspecified), gu.Ptr(""), nil
|
||||
case *application.LoginVersion_LoginV1:
|
||||
return gu.Ptr(domain.LoginVersion1), gu.Ptr(""), nil
|
||||
case *application.LoginVersion_LoginV2:
|
||||
_, err := url.Parse(v.LoginV2.GetBaseUri())
|
||||
return gu.Ptr(domain.LoginVersion2), gu.Ptr(v.LoginV2.GetBaseUri()), err
|
||||
default:
|
||||
return gu.Ptr(domain.LoginVersionUnspecified), gu.Ptr(""), nil
|
||||
}
|
||||
}
|
||||
|
||||
func loginVersionToPb(version domain.LoginVersion, baseURI *string) *application.LoginVersion {
|
||||
switch version {
|
||||
case domain.LoginVersionUnspecified:
|
||||
return nil
|
||||
case domain.LoginVersion1:
|
||||
return &application.LoginVersion{Version: &application.LoginVersion_LoginV1{LoginV1: &application.LoginV1{}}}
|
||||
case domain.LoginVersion2:
|
||||
return &application.LoginVersion{Version: &application.LoginVersion_LoginV2{LoginV2: &application.LoginV2{BaseUri: baseURI}}}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func appQueriesToModel(filters []*application.ApplicationSearchFilter) (queries []query.SearchQuery, err error) {
|
||||
queries = make([]query.SearchQuery, len(filters))
|
||||
for i, f := range filters {
|
||||
queries[i], err = applicationFilterToQuery(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return queries, nil
|
||||
}
|
||||
|
||||
func applicationFilterToQuery(applicationFilter *application.ApplicationSearchFilter) (query.SearchQuery, error) {
|
||||
switch q := applicationFilter.GetFilter().(type) {
|
||||
case *application.ApplicationSearchFilter_ProjectIdFilter:
|
||||
return query.NewAppProjectIDSearchQuery(q.ProjectIdFilter.GetProjectId())
|
||||
case *application.ApplicationSearchFilter_NameFilter:
|
||||
return query.NewAppNameSearchQuery(filter.TextMethodPbToQuery(q.NameFilter.GetMethod()), q.NameFilter.GetName())
|
||||
case *application.ApplicationSearchFilter_StateFilter:
|
||||
return query.NewAppStateSearchQuery(domain.AppState(q.StateFilter))
|
||||
case *application.ApplicationSearchFilter_TypeFilter:
|
||||
return applicationTypeFilterToQuery(q.TypeFilter)
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "CONV-z2mAGy", "List.Query.Invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func applicationTypeFilterToQuery(t application.ApplicationType) (*query.NotNullQuery, error) {
|
||||
switch t {
|
||||
case application.ApplicationType_APPLICATION_TYPE_OIDC:
|
||||
return query.NewNotNullQuery(query.AppOIDCConfigColumnAppID)
|
||||
case application.ApplicationType_APPLICATION_TYPE_API:
|
||||
return query.NewNotNullQuery(query.AppAPIConfigColumnAppID)
|
||||
case application.ApplicationType_APPLICATION_TYPE_SAML:
|
||||
return query.NewNotNullQuery(query.AppSAMLConfigColumnAppID)
|
||||
case application.ApplicationType_APPLICATION_TYPE_UNSPECIFIED:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "CONV-Jke83s", "List.Query.Invalid")
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "CONV-Skj3q", "List.Query.Invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func CreateAPIClientKeyRequestToDomain(key *application.CreateApplicationKeyRequest) *domain.ApplicationKey {
|
||||
return &domain.ApplicationKey{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: strings.TrimSpace(key.GetProjectId()),
|
||||
},
|
||||
ExpirationDate: key.GetExpirationDate().AsTime(),
|
||||
Type: domain.AuthNKeyTypeJSON,
|
||||
ApplicationID: strings.TrimSpace(key.GetApplicationId()),
|
||||
}
|
||||
}
|
||||
|
||||
func ListApplicationKeysRequestToDomain(sysDefaults systemdefaults.SystemDefaults, req *application.ListApplicationKeysRequest) (*query.AuthNKeySearchQueries, error) {
|
||||
var queries []query.SearchQuery
|
||||
|
||||
queries, err := ApplicationKeySearchQueriesToQuery(req.GetFilters())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
offset, limit, asc, err := filter.PaginationPbToQuery(sysDefaults, req.GetPagination())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &query.AuthNKeySearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
SortingColumn: appKeysSortingToColumn(req.GetSortingColumn()),
|
||||
},
|
||||
|
||||
Queries: queries,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ApplicationKeySearchQueriesToQuery(queries []*application.ApplicationKeySearchFilter) (_ []query.SearchQuery, err error) {
|
||||
searchQueries := make([]query.SearchQuery, len(queries))
|
||||
for i, query := range queries {
|
||||
searchQueries[i], err = ApplicationKeySearchFilterToQuery(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return searchQueries, nil
|
||||
}
|
||||
|
||||
func ApplicationKeySearchFilterToQuery(searchQuery *application.ApplicationKeySearchFilter) (query.SearchQuery, error) {
|
||||
switch f := searchQuery.GetFilter().(type) {
|
||||
case *application.ApplicationKeySearchFilter_ApplicationIdFilter:
|
||||
return query.NewAuthNKeyObjectIDQuery(strings.TrimSpace(f.ApplicationIdFilter.GetApplicationId()))
|
||||
case *application.ApplicationKeySearchFilter_OrganizationIdFilter:
|
||||
return query.NewAuthNKeyResourceOwnerQuery(strings.TrimSpace(f.OrganizationIdFilter.GetOrganizationId()))
|
||||
case *application.ApplicationKeySearchFilter_ProjectIdFilter:
|
||||
return query.NewAuthNKeyAggregateIDQuery(strings.TrimSpace(f.ProjectIdFilter.GetProjectId()))
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "CONV-t3ENme", "List.Query.Invalid")
|
||||
}
|
||||
}
|
||||
|
||||
func appKeysSortingToColumn(sortingCriteria application.ApplicationKeysSorting) query.Column {
|
||||
switch sortingCriteria {
|
||||
case application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_PROJECT_ID:
|
||||
return query.AuthNKeyColumnAggregateID
|
||||
case application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_CREATION_DATE:
|
||||
return query.AuthNKeyColumnCreationDate
|
||||
case application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_EXPIRATION:
|
||||
return query.AuthNKeyColumnExpiration
|
||||
case application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_ORGANIZATION_ID:
|
||||
return query.AuthNKeyColumnResourceOwner
|
||||
case application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_TYPE:
|
||||
return query.AuthNKeyColumnType
|
||||
case application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_APPLICATION_ID:
|
||||
return query.AuthNKeyColumnObjectID
|
||||
case application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_ID:
|
||||
fallthrough
|
||||
default:
|
||||
return query.AuthNKeyColumnID
|
||||
}
|
||||
}
|
||||
|
||||
func ApplicationKeysToPb(keys []*query.AuthNKey) []*application.ApplicationKey {
|
||||
pbAppKeys := make([]*application.ApplicationKey, len(keys))
|
||||
|
||||
for i, k := range keys {
|
||||
pbKey := &application.ApplicationKey{
|
||||
KeyId: k.ID,
|
||||
ApplicationId: k.ApplicationID,
|
||||
ProjectId: k.AggregateID,
|
||||
CreationDate: timestamppb.New(k.CreationDate),
|
||||
OrganizationId: k.ResourceOwner,
|
||||
ExpirationDate: timestamppb.New(k.Expiration),
|
||||
}
|
||||
pbAppKeys[i] = pbKey
|
||||
}
|
||||
|
||||
return pbAppKeys
|
||||
}
|
||||
@@ -0,0 +1,720 @@
|
||||
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"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/application/v2"
|
||||
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 *application.Application
|
||||
}{
|
||||
{
|
||||
testName: "full application conversion",
|
||||
inputQueryApp: &query.App{
|
||||
ID: "id",
|
||||
CreationDate: now,
|
||||
ChangeDate: now,
|
||||
State: domain.AppStateActive,
|
||||
Name: "test-application",
|
||||
APIConfig: &query.APIApp{},
|
||||
},
|
||||
expectedPbApp: &application.Application{
|
||||
ApplicationId: "id",
|
||||
CreationDate: timestamppb.New(now),
|
||||
ChangeDate: timestamppb.New(now),
|
||||
State: application.ApplicationState_APPLICATION_STATE_ACTIVE,
|
||||
Name: "test-application",
|
||||
Configuration: &application.Application_ApiConfiguration{
|
||||
ApiConfiguration: &application.APIConfiguration{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "nil application",
|
||||
inputQueryApp: nil,
|
||||
expectedPbApp: &application.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 *application.ListApplicationsRequest
|
||||
|
||||
expectedResponse *query.AppSearchQueries
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
testName: "invalid pagination limit",
|
||||
req: &application.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: &application.ListApplicationsRequest{
|
||||
Pagination: &filter_pb_v2.PaginationRequest{Asc: true},
|
||||
Filters: []*application.ApplicationSearchFilter{},
|
||||
},
|
||||
expectedResponse: &query.AppSearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
Asc: true,
|
||||
SortingColumn: query.AppColumnID,
|
||||
},
|
||||
Queries: []query.SearchQuery{},
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "valid request",
|
||||
req: &application.ListApplicationsRequest{
|
||||
Filters: []*application.ApplicationSearchFilter{
|
||||
{
|
||||
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{ProjectIdFilter: &application.ProjectIDFilter{ProjectId: "project1"}},
|
||||
},
|
||||
{
|
||||
Filter: &application.ApplicationSearchFilter_NameFilter{NameFilter: &application.ApplicationNameFilter{Name: "test"}},
|
||||
},
|
||||
},
|
||||
SortingColumn: application.ApplicationSorting_APPLICATION_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{
|
||||
validSearchByProjectQuery,
|
||||
validSearchByNameQuery,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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 TestApplicationSortingToColumn(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
sorting application.ApplicationSorting
|
||||
expected query.Column
|
||||
}{
|
||||
{
|
||||
name: "sort by change date",
|
||||
sorting: application.ApplicationSorting_APPLICATION_SORT_BY_CHANGE_DATE,
|
||||
expected: query.AppColumnChangeDate,
|
||||
},
|
||||
{
|
||||
name: "sort by creation date",
|
||||
sorting: application.ApplicationSorting_APPLICATION_SORT_BY_CREATION_DATE,
|
||||
expected: query.AppColumnCreationDate,
|
||||
},
|
||||
{
|
||||
name: "sort by name",
|
||||
sorting: application.ApplicationSorting_APPLICATION_SORT_BY_NAME,
|
||||
expected: query.AppColumnName,
|
||||
},
|
||||
{
|
||||
name: "sort by state",
|
||||
sorting: application.ApplicationSorting_APPLICATION_SORT_BY_STATE,
|
||||
expected: query.AppColumnState,
|
||||
},
|
||||
{
|
||||
name: "sort by ID",
|
||||
sorting: application.ApplicationSorting_APPLICATION_SORT_BY_ID,
|
||||
expected: query.AppColumnID,
|
||||
},
|
||||
{
|
||||
name: "unknown sorting defaults to ID",
|
||||
sorting: application.ApplicationSorting(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 application.ApplicationState
|
||||
}{
|
||||
{
|
||||
name: "active state",
|
||||
state: domain.AppStateActive,
|
||||
expected: application.ApplicationState_APPLICATION_STATE_ACTIVE,
|
||||
},
|
||||
{
|
||||
name: "inactive state",
|
||||
state: domain.AppStateInactive,
|
||||
expected: application.ApplicationState_APPLICATION_STATE_INACTIVE,
|
||||
},
|
||||
{
|
||||
name: "removed state",
|
||||
state: domain.AppStateRemoved,
|
||||
expected: application.ApplicationState_APPLICATION_STATE_REMOVED,
|
||||
},
|
||||
{
|
||||
name: "unspecified state",
|
||||
state: domain.AppStateUnspecified,
|
||||
expected: application.ApplicationState_APPLICATION_STATE_UNSPECIFIED,
|
||||
},
|
||||
{
|
||||
name: "unknown state defaults to unspecified",
|
||||
state: domain.AppState(99),
|
||||
expected: application.ApplicationState_APPLICATION_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 application.IsApplicationConfiguration
|
||||
}{
|
||||
{
|
||||
name: "OIDC config",
|
||||
app: &query.App{
|
||||
OIDCConfig: &query.OIDCApp{},
|
||||
},
|
||||
expected: &application.Application_OidcConfiguration{
|
||||
OidcConfiguration: &application.OIDCConfiguration{
|
||||
ResponseTypes: []application.OIDCResponseType{},
|
||||
GrantTypes: []application.OIDCGrantType{},
|
||||
ComplianceProblems: []*application.OIDCLocalizedMessage{},
|
||||
ClockSkew: &durationpb.Duration{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SAML config",
|
||||
app: &query.App{
|
||||
SAMLConfig: &query.SAMLApp{},
|
||||
},
|
||||
expected: &application.Application_SamlConfiguration{
|
||||
SamlConfiguration: &application.SAMLConfiguration{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "API config",
|
||||
app: &query.App{
|
||||
APIConfig: &query.APIApp{},
|
||||
},
|
||||
expected: &application.Application_ApiConfiguration{
|
||||
ApiConfiguration: &application.APIConfiguration{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
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 *application.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: &application.LoginVersion{Version: &application.LoginVersion_LoginV1{LoginV1: &application.LoginV1{}}},
|
||||
expectedVer: gu.Ptr(domain.LoginVersion1),
|
||||
expectedURI: gu.Ptr(""),
|
||||
},
|
||||
{
|
||||
name: "login v2 valid URI",
|
||||
version: &application.LoginVersion{Version: &application.LoginVersion_LoginV2{LoginV2: &application.LoginV2{BaseUri: gu.Ptr("https://valid.url")}}},
|
||||
expectedVer: gu.Ptr(domain.LoginVersion2),
|
||||
expectedURI: gu.Ptr("https://valid.url"),
|
||||
},
|
||||
{
|
||||
name: "login v2 invalid URI",
|
||||
version: &application.LoginVersion{Version: &application.LoginVersion_LoginV2{LoginV2: &application.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: &application.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 *application.LoginVersion
|
||||
}{
|
||||
{
|
||||
name: "unspecified version",
|
||||
version: domain.LoginVersionUnspecified,
|
||||
baseURI: nil,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "login v1",
|
||||
version: domain.LoginVersion1,
|
||||
baseURI: nil,
|
||||
expected: &application.LoginVersion{
|
||||
Version: &application.LoginVersion_LoginV1{
|
||||
LoginV1: &application.LoginV1{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "login v2",
|
||||
version: domain.LoginVersion2,
|
||||
baseURI: gu.Ptr("https://example.com"),
|
||||
expected: &application.LoginVersion{
|
||||
Version: &application.LoginVersion_LoginV2{
|
||||
LoginV2: &application.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 *application.ApplicationSearchFilter
|
||||
|
||||
expectedQuery query.SearchQuery
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "name query",
|
||||
query: &application.ApplicationSearchFilter{
|
||||
Filter: &application.ApplicationSearchFilter_NameFilter{
|
||||
NameFilter: &application.ApplicationNameFilter{
|
||||
Name: "test",
|
||||
Method: filter_pb_v2.TextFilterMethod_TEXT_FILTER_METHOD_EQUALS,
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedQuery: validAppNameSearchQuery,
|
||||
},
|
||||
{
|
||||
name: "state query",
|
||||
query: &application.ApplicationSearchFilter{
|
||||
Filter: &application.ApplicationSearchFilter_StateFilter{
|
||||
StateFilter: application.ApplicationState_APPLICATION_STATE_ACTIVE,
|
||||
},
|
||||
},
|
||||
expectedQuery: validAppStateSearchQuery,
|
||||
},
|
||||
{
|
||||
name: "api application only query",
|
||||
query: &application.ApplicationSearchFilter{
|
||||
Filter: &application.ApplicationSearchFilter_TypeFilter{
|
||||
TypeFilter: application.ApplicationType_APPLICATION_TYPE_API,
|
||||
},
|
||||
},
|
||||
expectedQuery: &query.NotNullQuery{
|
||||
Column: query.AppAPIConfigColumnAppID,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "oidc application only query",
|
||||
query: &application.ApplicationSearchFilter{
|
||||
Filter: &application.ApplicationSearchFilter_TypeFilter{
|
||||
TypeFilter: application.ApplicationType_APPLICATION_TYPE_OIDC,
|
||||
},
|
||||
},
|
||||
expectedQuery: &query.NotNullQuery{
|
||||
Column: query.AppOIDCConfigColumnAppID,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "saml application only query",
|
||||
query: &application.ApplicationSearchFilter{
|
||||
Filter: &application.ApplicationSearchFilter_TypeFilter{
|
||||
TypeFilter: application.ApplicationType_APPLICATION_TYPE_SAML,
|
||||
},
|
||||
},
|
||||
expectedQuery: &query.NotNullQuery{
|
||||
Column: query.AppSAMLConfigColumnAppID,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid query type",
|
||||
query: &application.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 := applicationFilterToQuery(tc.query)
|
||||
|
||||
// Then
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
assert.Equal(t, tc.expectedQuery, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListApplicationKeysRequestToDomain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
resourceOwnerQuery, err := query.NewAuthNKeyResourceOwnerQuery("org1")
|
||||
require.NoError(t, err)
|
||||
|
||||
projectIDQuery, err := query.NewAuthNKeyAggregateIDQuery("project1")
|
||||
require.NoError(t, err)
|
||||
|
||||
appIDQuery, err := query.NewAuthNKeyObjectIDQuery("app1")
|
||||
require.NoError(t, err)
|
||||
|
||||
sysDefaults := systemdefaults.SystemDefaults{DefaultQueryLimit: 100, MaxQueryLimit: 150}
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
req *application.ListApplicationKeysRequest
|
||||
|
||||
expectedResult *query.AuthNKeySearchQueries
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
name: "invalid pagination limit",
|
||||
req: &application.ListApplicationKeysRequest{
|
||||
Pagination: &filter_pb_v2.PaginationRequest{Asc: true, Limit: uint32(sysDefaults.MaxQueryLimit + 1)},
|
||||
},
|
||||
expectedResult: nil,
|
||||
expectedError: zerrors.ThrowInvalidArgumentf(fmt.Errorf("given: %d, allowed: %d", sysDefaults.MaxQueryLimit+1, sysDefaults.MaxQueryLimit), "QUERY-4M0fs", "Errors.Query.LimitExceeded"),
|
||||
},
|
||||
{
|
||||
name: "empty request",
|
||||
req: &application.ListApplicationKeysRequest{
|
||||
Pagination: &filter_pb_v2.PaginationRequest{Asc: true},
|
||||
},
|
||||
expectedResult: &query.AuthNKeySearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
Asc: true,
|
||||
SortingColumn: query.AuthNKeyColumnID,
|
||||
},
|
||||
Queries: []query.SearchQuery{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "only organization id",
|
||||
req: &application.ListApplicationKeysRequest{
|
||||
Pagination: &filter_pb_v2.PaginationRequest{Asc: true},
|
||||
Filters: []*application.ApplicationKeySearchFilter{
|
||||
{Filter: &application.ApplicationKeySearchFilter_OrganizationIdFilter{
|
||||
OrganizationIdFilter: &application.ApplicationKeyOrganizationIDFilter{OrganizationId: "org1"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expectedResult: &query.AuthNKeySearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
Asc: true,
|
||||
SortingColumn: query.AuthNKeyColumnID,
|
||||
},
|
||||
Queries: []query.SearchQuery{
|
||||
resourceOwnerQuery,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "only project id",
|
||||
req: &application.ListApplicationKeysRequest{
|
||||
Pagination: &filter_pb_v2.PaginationRequest{Asc: true},
|
||||
Filters: []*application.ApplicationKeySearchFilter{
|
||||
{Filter: &application.ApplicationKeySearchFilter_ProjectIdFilter{
|
||||
ProjectIdFilter: &application.ApplicationKeyProjectIDFilter{ProjectId: "project1"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expectedResult: &query.AuthNKeySearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
Asc: true,
|
||||
SortingColumn: query.AuthNKeyColumnID,
|
||||
},
|
||||
Queries: []query.SearchQuery{
|
||||
projectIDQuery,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "only application id",
|
||||
req: &application.ListApplicationKeysRequest{
|
||||
Pagination: &filter_pb_v2.PaginationRequest{Asc: true},
|
||||
Filters: []*application.ApplicationKeySearchFilter{
|
||||
{Filter: &application.ApplicationKeySearchFilter_ApplicationIdFilter{
|
||||
ApplicationIdFilter: &application.ApplicationKeyApplicationIDFilter{ApplicationId: "app1"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expectedResult: &query.AuthNKeySearchQueries{
|
||||
SearchRequest: query.SearchRequest{
|
||||
Offset: 0,
|
||||
Limit: 100,
|
||||
Asc: true,
|
||||
SortingColumn: query.AuthNKeyColumnID,
|
||||
},
|
||||
Queries: []query.SearchQuery{
|
||||
appIDQuery,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
result, err := ListApplicationKeysRequestToDomain(sysDefaults, tc.req)
|
||||
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
assert.Equal(t, tc.expectedResult, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplicationKeysToPb(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now()
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
input []*query.AuthNKey
|
||||
expected []*application.ApplicationKey
|
||||
}{
|
||||
{
|
||||
name: "multiple keys",
|
||||
input: []*query.AuthNKey{
|
||||
{
|
||||
ID: "key1",
|
||||
AggregateID: "project1",
|
||||
ApplicationID: "app1",
|
||||
CreationDate: now,
|
||||
ResourceOwner: "org1",
|
||||
Expiration: now.Add(24 * time.Hour),
|
||||
Type: domain.AuthNKeyTypeJSON,
|
||||
},
|
||||
{
|
||||
ID: "key2",
|
||||
AggregateID: "project2",
|
||||
ApplicationID: "app1",
|
||||
CreationDate: now.Add(-time.Hour),
|
||||
ResourceOwner: "org2",
|
||||
Expiration: now.Add(48 * time.Hour),
|
||||
Type: domain.AuthNKeyTypeNONE,
|
||||
},
|
||||
},
|
||||
expected: []*application.ApplicationKey{
|
||||
{
|
||||
KeyId: "key1",
|
||||
ApplicationId: "app1",
|
||||
ProjectId: "project1",
|
||||
CreationDate: timestamppb.New(now),
|
||||
OrganizationId: "org1",
|
||||
ExpirationDate: timestamppb.New(now.Add(24 * time.Hour)),
|
||||
},
|
||||
{
|
||||
KeyId: "key2",
|
||||
ApplicationId: "app1",
|
||||
ProjectId: "project2",
|
||||
CreationDate: timestamppb.New(now.Add(-time.Hour)),
|
||||
OrganizationId: "org2",
|
||||
ExpirationDate: timestamppb.New(now.Add(48 * time.Hour)),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "empty slice",
|
||||
input: []*query.AuthNKey{},
|
||||
expected: []*application.ApplicationKey{},
|
||||
},
|
||||
{
|
||||
name: "nil input",
|
||||
input: nil,
|
||||
expected: []*application.ApplicationKey{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
result := ApplicationKeysToPb(tc.input)
|
||||
assert.Equal(t, tc.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
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"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/application/v2"
|
||||
)
|
||||
|
||||
func CreateOIDCAppRequestToDomain(name, projectID string, req *application.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.GetApplicationType())),
|
||||
AuthMethodType: gu.Ptr(oidcAuthMethodTypeToDomain(req.GetAuthMethodType())),
|
||||
PostLogoutRedirectUris: req.GetPostLogoutRedirectUris(),
|
||||
DevMode: &req.DevelopmentMode,
|
||||
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 *application.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.ApplicationType),
|
||||
AuthMethodType: oidcAuthMethodTypeToDomainPtr(app.AuthMethodType),
|
||||
PostLogoutRedirectUris: app.PostLogoutRedirectUris,
|
||||
DevMode: app.DevelopmentMode,
|
||||
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 []application.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 application.OIDCResponseType_OIDC_RESPONSE_TYPE_UNSPECIFIED:
|
||||
oidcResponseTypes[i] = domain.OIDCResponseTypeUnspecified
|
||||
case application.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE:
|
||||
oidcResponseTypes[i] = domain.OIDCResponseTypeCode
|
||||
case application.OIDCResponseType_OIDC_RESPONSE_TYPE_ID_TOKEN:
|
||||
oidcResponseTypes[i] = domain.OIDCResponseTypeIDToken
|
||||
case application.OIDCResponseType_OIDC_RESPONSE_TYPE_ID_TOKEN_TOKEN:
|
||||
oidcResponseTypes[i] = domain.OIDCResponseTypeIDTokenToken
|
||||
}
|
||||
}
|
||||
return oidcResponseTypes
|
||||
}
|
||||
|
||||
func oidcGrantTypesToDomain(grantTypes []application.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 application.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE:
|
||||
oidcGrantTypes[i] = domain.OIDCGrantTypeAuthorizationCode
|
||||
case application.OIDCGrantType_OIDC_GRANT_TYPE_IMPLICIT:
|
||||
oidcGrantTypes[i] = domain.OIDCGrantTypeImplicit
|
||||
case application.OIDCGrantType_OIDC_GRANT_TYPE_REFRESH_TOKEN:
|
||||
oidcGrantTypes[i] = domain.OIDCGrantTypeRefreshToken
|
||||
case application.OIDCGrantType_OIDC_GRANT_TYPE_DEVICE_CODE:
|
||||
oidcGrantTypes[i] = domain.OIDCGrantTypeDeviceCode
|
||||
case application.OIDCGrantType_OIDC_GRANT_TYPE_TOKEN_EXCHANGE:
|
||||
oidcGrantTypes[i] = domain.OIDCGrantTypeTokenExchange
|
||||
}
|
||||
}
|
||||
return oidcGrantTypes
|
||||
}
|
||||
|
||||
func oidcApplicationTypeToDomainPtr(appType *application.OIDCApplicationType) *domain.OIDCApplicationType {
|
||||
if appType == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
res := oidcApplicationTypeToDomain(*appType)
|
||||
return &res
|
||||
}
|
||||
|
||||
func oidcApplicationTypeToDomain(appType application.OIDCApplicationType) domain.OIDCApplicationType {
|
||||
switch appType {
|
||||
case application.OIDCApplicationType_OIDC_APP_TYPE_WEB:
|
||||
return domain.OIDCApplicationTypeWeb
|
||||
case application.OIDCApplicationType_OIDC_APP_TYPE_USER_AGENT:
|
||||
return domain.OIDCApplicationTypeUserAgent
|
||||
case application.OIDCApplicationType_OIDC_APP_TYPE_NATIVE:
|
||||
return domain.OIDCApplicationTypeNative
|
||||
default:
|
||||
return domain.OIDCApplicationTypeWeb
|
||||
}
|
||||
}
|
||||
|
||||
func oidcAuthMethodTypeToDomainPtr(authType *application.OIDCAuthMethodType) *domain.OIDCAuthMethodType {
|
||||
if authType == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
res := oidcAuthMethodTypeToDomain(*authType)
|
||||
return &res
|
||||
}
|
||||
|
||||
func oidcAuthMethodTypeToDomain(authType application.OIDCAuthMethodType) domain.OIDCAuthMethodType {
|
||||
switch authType {
|
||||
case application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC:
|
||||
return domain.OIDCAuthMethodTypeBasic
|
||||
case application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_POST:
|
||||
return domain.OIDCAuthMethodTypePost
|
||||
case application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE:
|
||||
return domain.OIDCAuthMethodTypeNone
|
||||
case application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT:
|
||||
return domain.OIDCAuthMethodTypePrivateKeyJWT
|
||||
default:
|
||||
return domain.OIDCAuthMethodTypeBasic
|
||||
}
|
||||
}
|
||||
|
||||
func oidcTokenTypeToDomainPtr(tokenType *application.OIDCTokenType) *domain.OIDCTokenType {
|
||||
if tokenType == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
res := oidcTokenTypeToDomain(*tokenType)
|
||||
return &res
|
||||
}
|
||||
|
||||
func oidcTokenTypeToDomain(tokenType application.OIDCTokenType) domain.OIDCTokenType {
|
||||
switch tokenType {
|
||||
case application.OIDCTokenType_OIDC_TOKEN_TYPE_BEARER:
|
||||
return domain.OIDCTokenTypeBearer
|
||||
case application.OIDCTokenType_OIDC_TOKEN_TYPE_JWT:
|
||||
return domain.OIDCTokenTypeJWT
|
||||
default:
|
||||
return domain.OIDCTokenTypeBearer
|
||||
}
|
||||
}
|
||||
|
||||
func ComplianceProblemsToLocalizedMessages(complianceProblems []string) []*application.OIDCLocalizedMessage {
|
||||
converted := make([]*application.OIDCLocalizedMessage, len(complianceProblems))
|
||||
for i, p := range complianceProblems {
|
||||
converted[i] = &application.OIDCLocalizedMessage{Key: p}
|
||||
}
|
||||
|
||||
return converted
|
||||
}
|
||||
|
||||
func appOIDCConfigToPb(oidcApp *query.OIDCApp) *application.Application_OidcConfiguration {
|
||||
return &application.Application_OidcConfiguration{
|
||||
OidcConfiguration: &application.OIDCConfiguration{
|
||||
RedirectUris: oidcApp.RedirectURIs,
|
||||
ResponseTypes: oidcResponseTypesFromModel(oidcApp.ResponseTypes),
|
||||
GrantTypes: oidcGrantTypesFromModel(oidcApp.GrantTypes),
|
||||
ApplicationType: oidcApplicationTypeToPb(oidcApp.AppType),
|
||||
ClientId: oidcApp.ClientID,
|
||||
AuthMethodType: oidcAuthMethodTypeToPb(oidcApp.AuthMethodType),
|
||||
PostLogoutRedirectUris: oidcApp.PostLogoutRedirectURIs,
|
||||
Version: application.OIDCVersion_OIDC_VERSION_1_0,
|
||||
NonCompliant: len(oidcApp.ComplianceProblems) != 0,
|
||||
ComplianceProblems: ComplianceProblemsToLocalizedMessages(oidcApp.ComplianceProblems),
|
||||
DevelopmentMode: 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) []application.OIDCResponseType {
|
||||
oidcResponseTypes := make([]application.OIDCResponseType, len(responseTypes))
|
||||
for i, responseType := range responseTypes {
|
||||
switch responseType {
|
||||
case domain.OIDCResponseTypeUnspecified:
|
||||
oidcResponseTypes[i] = application.OIDCResponseType_OIDC_RESPONSE_TYPE_UNSPECIFIED
|
||||
case domain.OIDCResponseTypeCode:
|
||||
oidcResponseTypes[i] = application.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE
|
||||
case domain.OIDCResponseTypeIDToken:
|
||||
oidcResponseTypes[i] = application.OIDCResponseType_OIDC_RESPONSE_TYPE_ID_TOKEN
|
||||
case domain.OIDCResponseTypeIDTokenToken:
|
||||
oidcResponseTypes[i] = application.OIDCResponseType_OIDC_RESPONSE_TYPE_ID_TOKEN_TOKEN
|
||||
}
|
||||
}
|
||||
return oidcResponseTypes
|
||||
}
|
||||
|
||||
func oidcGrantTypesFromModel(grantTypes []domain.OIDCGrantType) []application.OIDCGrantType {
|
||||
oidcGrantTypes := make([]application.OIDCGrantType, len(grantTypes))
|
||||
for i, grantType := range grantTypes {
|
||||
switch grantType {
|
||||
case domain.OIDCGrantTypeAuthorizationCode:
|
||||
oidcGrantTypes[i] = application.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE
|
||||
case domain.OIDCGrantTypeImplicit:
|
||||
oidcGrantTypes[i] = application.OIDCGrantType_OIDC_GRANT_TYPE_IMPLICIT
|
||||
case domain.OIDCGrantTypeRefreshToken:
|
||||
oidcGrantTypes[i] = application.OIDCGrantType_OIDC_GRANT_TYPE_REFRESH_TOKEN
|
||||
case domain.OIDCGrantTypeDeviceCode:
|
||||
oidcGrantTypes[i] = application.OIDCGrantType_OIDC_GRANT_TYPE_DEVICE_CODE
|
||||
case domain.OIDCGrantTypeTokenExchange:
|
||||
oidcGrantTypes[i] = application.OIDCGrantType_OIDC_GRANT_TYPE_TOKEN_EXCHANGE
|
||||
}
|
||||
}
|
||||
return oidcGrantTypes
|
||||
}
|
||||
|
||||
func oidcApplicationTypeToPb(appType domain.OIDCApplicationType) application.OIDCApplicationType {
|
||||
switch appType {
|
||||
case domain.OIDCApplicationTypeWeb:
|
||||
return application.OIDCApplicationType_OIDC_APP_TYPE_WEB
|
||||
case domain.OIDCApplicationTypeUserAgent:
|
||||
return application.OIDCApplicationType_OIDC_APP_TYPE_USER_AGENT
|
||||
case domain.OIDCApplicationTypeNative:
|
||||
return application.OIDCApplicationType_OIDC_APP_TYPE_NATIVE
|
||||
default:
|
||||
return application.OIDCApplicationType_OIDC_APP_TYPE_WEB
|
||||
}
|
||||
}
|
||||
|
||||
func oidcAuthMethodTypeToPb(authType domain.OIDCAuthMethodType) application.OIDCAuthMethodType {
|
||||
switch authType {
|
||||
case domain.OIDCAuthMethodTypeBasic:
|
||||
return application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC
|
||||
case domain.OIDCAuthMethodTypePost:
|
||||
return application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_POST
|
||||
case domain.OIDCAuthMethodTypeNone:
|
||||
return application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE
|
||||
case domain.OIDCAuthMethodTypePrivateKeyJWT:
|
||||
return application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT
|
||||
default:
|
||||
return application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC
|
||||
}
|
||||
}
|
||||
|
||||
func oidcTokenTypeToPb(tokenType domain.OIDCTokenType) application.OIDCTokenType {
|
||||
switch tokenType {
|
||||
case domain.OIDCTokenTypeBearer:
|
||||
return application.OIDCTokenType_OIDC_TOKEN_TYPE_BEARER
|
||||
case domain.OIDCTokenTypeJWT:
|
||||
return application.OIDCTokenType_OIDC_TOKEN_TYPE_JWT
|
||||
default:
|
||||
return application.OIDCTokenType_OIDC_TOKEN_TYPE_BEARER
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/application/v2"
|
||||
)
|
||||
|
||||
func TestCreateOIDCAppRequestToDomain(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
projectID string
|
||||
req *application.CreateOIDCApplicationRequest
|
||||
|
||||
expectedModel *domain.OIDCApp
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
testName: "unparsable login version 2 URL",
|
||||
projectID: "pid",
|
||||
req: &application.CreateOIDCApplicationRequest{
|
||||
LoginVersion: &application.LoginVersion{Version: &application.LoginVersion_LoginV2{
|
||||
LoginV2: &application.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: &application.CreateOIDCApplicationRequest{
|
||||
RedirectUris: []string{"https://redirect"},
|
||||
ResponseTypes: []application.OIDCResponseType{application.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE},
|
||||
GrantTypes: []application.OIDCGrantType{application.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE},
|
||||
ApplicationType: application.OIDCApplicationType_OIDC_APP_TYPE_WEB,
|
||||
AuthMethodType: application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC,
|
||||
PostLogoutRedirectUris: []string{"https://logout"},
|
||||
DevelopmentMode: true,
|
||||
AccessTokenType: application.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: &application.LoginVersion{Version: &application.LoginVersion_LoginV2{LoginV2: &application.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 *application.UpdateOIDCApplicationConfigurationRequest
|
||||
|
||||
expectedModel *domain.OIDCApp
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
testName: "unparsable login version 2 URL",
|
||||
appID: "app1",
|
||||
projectID: "pid",
|
||||
req: &application.UpdateOIDCApplicationConfigurationRequest{
|
||||
LoginVersion: &application.LoginVersion{Version: &application.LoginVersion_LoginV2{
|
||||
LoginV2: &application.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: &application.UpdateOIDCApplicationConfigurationRequest{
|
||||
RedirectUris: []string{"https://redirect"},
|
||||
ResponseTypes: []application.OIDCResponseType{application.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE},
|
||||
GrantTypes: []application.OIDCGrantType{application.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE},
|
||||
ApplicationType: gu.Ptr(application.OIDCApplicationType_OIDC_APP_TYPE_WEB),
|
||||
AuthMethodType: gu.Ptr(application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC),
|
||||
PostLogoutRedirectUris: []string{"https://logout"},
|
||||
DevelopmentMode: gu.Ptr(true),
|
||||
AccessTokenType: gu.Ptr(application.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: &application.LoginVersion{Version: &application.LoginVersion_LoginV2{
|
||||
LoginV2: &application.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 []application.OIDCResponseType
|
||||
expectedResponse []domain.OIDCResponseType
|
||||
}{
|
||||
{
|
||||
testName: "empty response types",
|
||||
inputResponseType: []application.OIDCResponseType{},
|
||||
expectedResponse: []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
|
||||
},
|
||||
{
|
||||
testName: "all response types",
|
||||
inputResponseType: []application.OIDCResponseType{
|
||||
application.OIDCResponseType_OIDC_RESPONSE_TYPE_UNSPECIFIED,
|
||||
application.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE,
|
||||
application.OIDCResponseType_OIDC_RESPONSE_TYPE_ID_TOKEN,
|
||||
application.OIDCResponseType_OIDC_RESPONSE_TYPE_ID_TOKEN_TOKEN,
|
||||
},
|
||||
expectedResponse: []domain.OIDCResponseType{
|
||||
domain.OIDCResponseTypeUnspecified,
|
||||
domain.OIDCResponseTypeCode,
|
||||
domain.OIDCResponseTypeIDToken,
|
||||
domain.OIDCResponseTypeIDTokenToken,
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "single response type",
|
||||
inputResponseType: []application.OIDCResponseType{
|
||||
application.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 []application.OIDCGrantType
|
||||
expectedGrants []domain.OIDCGrantType
|
||||
}{
|
||||
{
|
||||
testName: "empty grant types",
|
||||
inputGrantType: []application.OIDCGrantType{},
|
||||
expectedGrants: []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
|
||||
},
|
||||
{
|
||||
testName: "all grant types",
|
||||
inputGrantType: []application.OIDCGrantType{
|
||||
application.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE,
|
||||
application.OIDCGrantType_OIDC_GRANT_TYPE_IMPLICIT,
|
||||
application.OIDCGrantType_OIDC_GRANT_TYPE_REFRESH_TOKEN,
|
||||
application.OIDCGrantType_OIDC_GRANT_TYPE_DEVICE_CODE,
|
||||
application.OIDCGrantType_OIDC_GRANT_TYPE_TOKEN_EXCHANGE,
|
||||
},
|
||||
expectedGrants: []domain.OIDCGrantType{
|
||||
domain.OIDCGrantTypeAuthorizationCode,
|
||||
domain.OIDCGrantTypeImplicit,
|
||||
domain.OIDCGrantTypeRefreshToken,
|
||||
domain.OIDCGrantTypeDeviceCode,
|
||||
domain.OIDCGrantTypeTokenExchange,
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "single grant type",
|
||||
inputGrantType: []application.OIDCGrantType{
|
||||
application.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 application.OIDCApplicationType
|
||||
expected domain.OIDCApplicationType
|
||||
}{
|
||||
{
|
||||
name: "web type",
|
||||
appType: application.OIDCApplicationType_OIDC_APP_TYPE_WEB,
|
||||
expected: domain.OIDCApplicationTypeWeb,
|
||||
},
|
||||
{
|
||||
name: "user agent type",
|
||||
appType: application.OIDCApplicationType_OIDC_APP_TYPE_USER_AGENT,
|
||||
expected: domain.OIDCApplicationTypeUserAgent,
|
||||
},
|
||||
{
|
||||
name: "native type",
|
||||
appType: application.OIDCApplicationType_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 application.OIDCAuthMethodType
|
||||
expectedResponse domain.OIDCAuthMethodType
|
||||
}{
|
||||
{
|
||||
name: "basic auth type",
|
||||
authType: application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC,
|
||||
expectedResponse: domain.OIDCAuthMethodTypeBasic,
|
||||
},
|
||||
{
|
||||
name: "post auth type",
|
||||
authType: application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_POST,
|
||||
expectedResponse: domain.OIDCAuthMethodTypePost,
|
||||
},
|
||||
{
|
||||
name: "none auth type",
|
||||
authType: application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE,
|
||||
expectedResponse: domain.OIDCAuthMethodTypeNone,
|
||||
},
|
||||
{
|
||||
name: "private key jwt auth type",
|
||||
authType: application.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 application.OIDCTokenType
|
||||
expectedType domain.OIDCTokenType
|
||||
}{
|
||||
{
|
||||
name: "bearer token type",
|
||||
tokenType: application.OIDCTokenType_OIDC_TOKEN_TYPE_BEARER,
|
||||
expectedType: domain.OIDCTokenTypeBearer,
|
||||
},
|
||||
{
|
||||
name: "jwt token type",
|
||||
tokenType: application.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 *application.Application_OidcConfiguration
|
||||
}{
|
||||
{
|
||||
name: "empty config",
|
||||
input: &query.OIDCApp{},
|
||||
expected: &application.Application_OidcConfiguration{
|
||||
OidcConfiguration: &application.OIDCConfiguration{
|
||||
Version: application.OIDCVersion_OIDC_VERSION_1_0,
|
||||
ComplianceProblems: []*application.OIDCLocalizedMessage{},
|
||||
ClockSkew: durationpb.New(0),
|
||||
ResponseTypes: []application.OIDCResponseType{},
|
||||
GrantTypes: []application.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: &application.Application_OidcConfiguration{
|
||||
OidcConfiguration: &application.OIDCConfiguration{
|
||||
RedirectUris: []string{"https://example.com/callback"},
|
||||
ResponseTypes: []application.OIDCResponseType{application.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE},
|
||||
GrantTypes: []application.OIDCGrantType{application.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE},
|
||||
ApplicationType: application.OIDCApplicationType_OIDC_APP_TYPE_WEB,
|
||||
ClientId: "client123",
|
||||
AuthMethodType: application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC,
|
||||
PostLogoutRedirectUris: []string{"https://example.com/logout"},
|
||||
Version: application.OIDCVersion_OIDC_VERSION_1_0,
|
||||
NonCompliant: true,
|
||||
ComplianceProblems: []*application.OIDCLocalizedMessage{
|
||||
{Key: "problem1"},
|
||||
{Key: "problem2"},
|
||||
},
|
||||
DevelopmentMode: true,
|
||||
AccessTokenType: application.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: &application.LoginVersion{
|
||||
Version: &application.LoginVersion_LoginV2{
|
||||
LoginV2: &application.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 []application.OIDCResponseType
|
||||
}{
|
||||
{
|
||||
name: "empty response types",
|
||||
responseTypes: []domain.OIDCResponseType{},
|
||||
expected: []application.OIDCResponseType{},
|
||||
},
|
||||
{
|
||||
name: "all response types",
|
||||
responseTypes: []domain.OIDCResponseType{
|
||||
domain.OIDCResponseTypeUnspecified,
|
||||
domain.OIDCResponseTypeCode,
|
||||
domain.OIDCResponseTypeIDToken,
|
||||
domain.OIDCResponseTypeIDTokenToken,
|
||||
},
|
||||
expected: []application.OIDCResponseType{
|
||||
application.OIDCResponseType_OIDC_RESPONSE_TYPE_UNSPECIFIED,
|
||||
application.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE,
|
||||
application.OIDCResponseType_OIDC_RESPONSE_TYPE_ID_TOKEN,
|
||||
application.OIDCResponseType_OIDC_RESPONSE_TYPE_ID_TOKEN_TOKEN,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "single response type",
|
||||
responseTypes: []domain.OIDCResponseType{
|
||||
domain.OIDCResponseTypeCode,
|
||||
},
|
||||
expected: []application.OIDCResponseType{
|
||||
application.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 []application.OIDCGrantType
|
||||
}{
|
||||
{
|
||||
name: "empty grant types",
|
||||
grantTypes: []domain.OIDCGrantType{},
|
||||
expected: []application.OIDCGrantType{},
|
||||
},
|
||||
{
|
||||
name: "all grant types",
|
||||
grantTypes: []domain.OIDCGrantType{
|
||||
domain.OIDCGrantTypeAuthorizationCode,
|
||||
domain.OIDCGrantTypeImplicit,
|
||||
domain.OIDCGrantTypeRefreshToken,
|
||||
domain.OIDCGrantTypeDeviceCode,
|
||||
domain.OIDCGrantTypeTokenExchange,
|
||||
},
|
||||
expected: []application.OIDCGrantType{
|
||||
application.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE,
|
||||
application.OIDCGrantType_OIDC_GRANT_TYPE_IMPLICIT,
|
||||
application.OIDCGrantType_OIDC_GRANT_TYPE_REFRESH_TOKEN,
|
||||
application.OIDCGrantType_OIDC_GRANT_TYPE_DEVICE_CODE,
|
||||
application.OIDCGrantType_OIDC_GRANT_TYPE_TOKEN_EXCHANGE,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "single grant type",
|
||||
grantTypes: []domain.OIDCGrantType{
|
||||
domain.OIDCGrantTypeAuthorizationCode,
|
||||
},
|
||||
expected: []application.OIDCGrantType{
|
||||
application.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 application.OIDCApplicationType
|
||||
}{
|
||||
{
|
||||
name: "web type",
|
||||
appType: domain.OIDCApplicationTypeWeb,
|
||||
expected: application.OIDCApplicationType_OIDC_APP_TYPE_WEB,
|
||||
},
|
||||
{
|
||||
name: "user agent type",
|
||||
appType: domain.OIDCApplicationTypeUserAgent,
|
||||
expected: application.OIDCApplicationType_OIDC_APP_TYPE_USER_AGENT,
|
||||
},
|
||||
{
|
||||
name: "native type",
|
||||
appType: domain.OIDCApplicationTypeNative,
|
||||
expected: application.OIDCApplicationType_OIDC_APP_TYPE_NATIVE,
|
||||
},
|
||||
{
|
||||
name: "unspecified type defaults to web",
|
||||
appType: domain.OIDCApplicationType(999), // Invalid value to trigger default case
|
||||
expected: application.OIDCApplicationType_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 application.OIDCAuthMethodType
|
||||
}{
|
||||
{
|
||||
name: "basic auth type",
|
||||
authType: domain.OIDCAuthMethodTypeBasic,
|
||||
expected: application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC,
|
||||
},
|
||||
{
|
||||
name: "post auth type",
|
||||
authType: domain.OIDCAuthMethodTypePost,
|
||||
expected: application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_POST,
|
||||
},
|
||||
{
|
||||
name: "none auth type",
|
||||
authType: domain.OIDCAuthMethodTypeNone,
|
||||
expected: application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_NONE,
|
||||
},
|
||||
{
|
||||
name: "private key jwt auth type",
|
||||
authType: domain.OIDCAuthMethodTypePrivateKeyJWT,
|
||||
expected: application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT,
|
||||
},
|
||||
{
|
||||
name: "unknown auth type defaults to basic",
|
||||
authType: domain.OIDCAuthMethodType(999),
|
||||
expected: application.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 application.OIDCTokenType
|
||||
}{
|
||||
{
|
||||
name: "bearer token type",
|
||||
tokenType: domain.OIDCTokenTypeBearer,
|
||||
expected: application.OIDCTokenType_OIDC_TOKEN_TYPE_BEARER,
|
||||
},
|
||||
{
|
||||
name: "jwt token type",
|
||||
tokenType: domain.OIDCTokenTypeJWT,
|
||||
expected: application.OIDCTokenType_OIDC_TOKEN_TYPE_JWT,
|
||||
},
|
||||
{
|
||||
name: "unknown token type defaults to bearer",
|
||||
tokenType: domain.OIDCTokenType(999), // Invalid value to trigger default case
|
||||
expected: application.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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/application/v2"
|
||||
)
|
||||
|
||||
func CreateSAMLAppRequestToDomain(name, projectID string, req *application.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 *application.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 application.MetaType) ([]byte, *string) {
|
||||
switch t := metas.(type) {
|
||||
case *application.UpdateSAMLApplicationConfigurationRequest_MetadataXml:
|
||||
return t.MetadataXml, nil
|
||||
case *application.UpdateSAMLApplicationConfigurationRequest_MetadataUrl:
|
||||
return nil, &t.MetadataUrl
|
||||
case nil:
|
||||
return nil, nil
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func appSAMLConfigToPb(samlApp *query.SAMLApp) application.IsApplicationConfiguration {
|
||||
if samlApp == nil {
|
||||
return &application.Application_SamlConfiguration{
|
||||
SamlConfiguration: &application.SAMLConfiguration{
|
||||
LoginVersion: &application.LoginVersion{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return &application.Application_SamlConfiguration{
|
||||
SamlConfiguration: &application.SAMLConfiguration{
|
||||
MetadataXml: samlApp.Metadata,
|
||||
MetadataUrl: samlApp.MetadataURL,
|
||||
LoginVersion: loginVersionToPb(samlApp.LoginVersion, samlApp.LoginBaseURI),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"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/integration"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/application/v2"
|
||||
)
|
||||
|
||||
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(integration.URL())
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
appName string
|
||||
projectID string
|
||||
req *application.CreateSAMLApplicationRequest
|
||||
|
||||
expectedResponse *domain.SAMLApp
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
testName: "login version error",
|
||||
appName: "test-application",
|
||||
projectID: "proj-1",
|
||||
req: &application.CreateSAMLApplicationRequest{
|
||||
Metadata: &application.CreateSAMLApplicationRequest_MetadataXml{
|
||||
MetadataXml: samlMetadataGen(integration.URL()),
|
||||
},
|
||||
LoginVersion: &application.LoginVersion{
|
||||
Version: &application.LoginVersion_LoginV2{
|
||||
LoginV2: &application.LoginV2{BaseUri: gu.Ptr("%+o")},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: &url.Error{
|
||||
URL: "%+o",
|
||||
Op: "parse",
|
||||
Err: url.EscapeError("%+o"),
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "valid request",
|
||||
appName: "test-application",
|
||||
projectID: "proj-1",
|
||||
req: &application.CreateSAMLApplicationRequest{
|
||||
Metadata: &application.CreateSAMLApplicationRequest_MetadataXml{
|
||||
MetadataXml: genMetaForValidRequest,
|
||||
},
|
||||
LoginVersion: nil,
|
||||
},
|
||||
|
||||
expectedResponse: &domain.SAMLApp{
|
||||
ObjectRoot: models.ObjectRoot{AggregateID: "proj-1"},
|
||||
AppName: "test-application",
|
||||
Metadata: genMetaForValidRequest,
|
||||
MetadataURL: gu.Ptr(""),
|
||||
LoginVersion: gu.Ptr(domain.LoginVersionUnspecified),
|
||||
LoginBaseURI: gu.Ptr(""),
|
||||
State: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "nil request",
|
||||
appName: "test-application",
|
||||
projectID: "proj-1",
|
||||
req: nil,
|
||||
|
||||
expectedResponse: &domain.SAMLApp{
|
||||
AppName: "test-application",
|
||||
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(integration.URL())
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
appID string
|
||||
projectID string
|
||||
req *application.UpdateSAMLApplicationConfigurationRequest
|
||||
|
||||
expectedResponse *domain.SAMLApp
|
||||
expectedError error
|
||||
}{
|
||||
{
|
||||
testName: "login version error",
|
||||
appID: "application-1",
|
||||
projectID: "proj-1",
|
||||
req: &application.UpdateSAMLApplicationConfigurationRequest{
|
||||
Metadata: &application.UpdateSAMLApplicationConfigurationRequest_MetadataXml{
|
||||
MetadataXml: samlMetadataGen(integration.URL()),
|
||||
},
|
||||
LoginVersion: &application.LoginVersion{
|
||||
Version: &application.LoginVersion_LoginV2{
|
||||
LoginV2: &application.LoginV2{BaseUri: gu.Ptr("%+o")},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedError: &url.Error{
|
||||
URL: "%+o",
|
||||
Op: "parse",
|
||||
Err: url.EscapeError("%+o"),
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "valid request",
|
||||
appID: "application-1",
|
||||
projectID: "proj-1",
|
||||
req: &application.UpdateSAMLApplicationConfigurationRequest{
|
||||
Metadata: &application.UpdateSAMLApplicationConfigurationRequest_MetadataXml{
|
||||
MetadataXml: genMetaForValidRequest,
|
||||
},
|
||||
LoginVersion: nil,
|
||||
},
|
||||
expectedResponse: &domain.SAMLApp{
|
||||
ObjectRoot: models.ObjectRoot{AggregateID: "proj-1"},
|
||||
AppID: "application-1",
|
||||
Metadata: genMetaForValidRequest,
|
||||
LoginVersion: gu.Ptr(domain.LoginVersionUnspecified),
|
||||
LoginBaseURI: gu.Ptr(""),
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "nil request",
|
||||
appID: "application-1",
|
||||
projectID: "proj-1",
|
||||
req: nil,
|
||||
expectedResponse: &domain.SAMLApp{
|
||||
ObjectRoot: models.ObjectRoot{AggregateID: "proj-1"},
|
||||
AppID: "application-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(integration.URL())
|
||||
|
||||
tt := []struct {
|
||||
name string
|
||||
inputSAMLApp *query.SAMLApp
|
||||
|
||||
expectedPbApp application.IsApplicationConfiguration
|
||||
}{
|
||||
{
|
||||
name: "valid conversion",
|
||||
inputSAMLApp: &query.SAMLApp{
|
||||
Metadata: metadata,
|
||||
LoginVersion: domain.LoginVersion2,
|
||||
LoginBaseURI: gu.Ptr("https://example.com"),
|
||||
},
|
||||
expectedPbApp: &application.Application_SamlConfiguration{
|
||||
SamlConfiguration: &application.SAMLConfiguration{
|
||||
MetadataXml: metadata,
|
||||
LoginVersion: &application.LoginVersion{
|
||||
Version: &application.LoginVersion_LoginV2{
|
||||
LoginV2: &application.LoginV2{BaseUri: gu.Ptr("https://example.com")},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nil saml application",
|
||||
inputSAMLApp: nil,
|
||||
expectedPbApp: &application.Application_SamlConfiguration{
|
||||
SamlConfiguration: &application.SAMLConfiguration{
|
||||
LoginVersion: &application.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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
//go:build integration
|
||||
|
||||
package app_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/application/v2"
|
||||
)
|
||||
|
||||
func TestCreateApplicationKey(t *testing.T) {
|
||||
p, projectOwnerCtx := getProjectAndProjectContext(t, instance, IAMOwnerCtx)
|
||||
createdApp := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
|
||||
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
creationRequest *application.CreateApplicationKeyRequest
|
||||
inputCtx context.Context
|
||||
|
||||
expectedErrorType codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when application id is not found should return failed precondition",
|
||||
inputCtx: IAMOwnerCtx,
|
||||
creationRequest: &application.CreateApplicationKeyRequest{
|
||||
ProjectId: p.GetId(),
|
||||
ApplicationId: integration.ID(),
|
||||
ExpirationDate: timestamppb.New(time.Now().AddDate(0, 0, 1).UTC()),
|
||||
},
|
||||
expectedErrorType: codes.FailedPrecondition,
|
||||
},
|
||||
{
|
||||
testName: "when CreateAPIApp request is valid should create application and return no error",
|
||||
inputCtx: IAMOwnerCtx,
|
||||
creationRequest: &application.CreateApplicationKeyRequest{
|
||||
ProjectId: p.GetId(),
|
||||
ApplicationId: createdApp.GetApplicationId(),
|
||||
ExpirationDate: timestamppb.New(time.Now().AddDate(0, 0, 1).UTC()),
|
||||
},
|
||||
},
|
||||
|
||||
// LoginUser
|
||||
{
|
||||
testName: "when user has no project.application.write permission for application key generation should return permission error",
|
||||
inputCtx: LoginUserCtx,
|
||||
creationRequest: &application.CreateApplicationKeyRequest{
|
||||
ProjectId: p.GetId(),
|
||||
ApplicationId: createdApp.GetApplicationId(),
|
||||
ExpirationDate: timestamppb.New(time.Now().AddDate(0, 0, 1).UTC()),
|
||||
},
|
||||
expectedErrorType: codes.PermissionDenied,
|
||||
},
|
||||
|
||||
// OrgOwner
|
||||
{
|
||||
testName: "when user is OrgOwner application key request should succeed",
|
||||
inputCtx: OrgOwnerCtx,
|
||||
creationRequest: &application.CreateApplicationKeyRequest{
|
||||
ProjectId: p.GetId(),
|
||||
ApplicationId: createdApp.GetApplicationId(),
|
||||
ExpirationDate: timestamppb.New(time.Now().AddDate(0, 0, 1).UTC()),
|
||||
},
|
||||
},
|
||||
|
||||
// ProjectOwner
|
||||
{
|
||||
testName: "when user is ProjectOwner application key request should succeed",
|
||||
inputCtx: projectOwnerCtx,
|
||||
creationRequest: &application.CreateApplicationKeyRequest{
|
||||
ProjectId: p.GetId(),
|
||||
ApplicationId: createdApp.GetApplicationId(),
|
||||
ExpirationDate: timestamppb.New(time.Now().AddDate(0, 0, 1).UTC()),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
res, err := instance.Client.ApplicationV2.CreateApplicationKey(tc.inputCtx, tc.creationRequest)
|
||||
|
||||
require.Equal(t, tc.expectedErrorType, status.Code(err))
|
||||
if tc.expectedErrorType == codes.OK {
|
||||
assert.NotZero(t, res.GetKeyId())
|
||||
assert.NotZero(t, res.GetCreationDate())
|
||||
assert.NotZero(t, res.GetKeyDetails())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteApplicationKey(t *testing.T) {
|
||||
p, projectOwnerCtx := getProjectAndProjectContext(t, instance, IAMOwnerCtx)
|
||||
createdApp := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
|
||||
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
deletionRequest func(ttt *testing.T) *application.DeleteApplicationKeyRequest
|
||||
inputCtx context.Context
|
||||
|
||||
expectedErrorType codes.Code
|
||||
}{
|
||||
{
|
||||
testName: "when application key ID is not found should return not found error",
|
||||
inputCtx: IAMOwnerCtx,
|
||||
deletionRequest: func(ttt *testing.T) *application.DeleteApplicationKeyRequest {
|
||||
return &application.DeleteApplicationKeyRequest{
|
||||
KeyId: integration.ID(),
|
||||
ProjectId: p.GetId(),
|
||||
ApplicationId: createdApp.GetApplicationId(),
|
||||
}
|
||||
},
|
||||
expectedErrorType: codes.NotFound,
|
||||
},
|
||||
{
|
||||
testName: "when valid application key ID should delete successfully",
|
||||
inputCtx: IAMOwnerCtx,
|
||||
deletionRequest: func(ttt *testing.T) *application.DeleteApplicationKeyRequest {
|
||||
createdAppKey := createAppKey(ttt, IAMOwnerCtx, instance, p.GetId(), createdApp.GetApplicationId(), time.Now().AddDate(0, 0, 1))
|
||||
|
||||
return &application.DeleteApplicationKeyRequest{
|
||||
KeyId: createdAppKey.GetKeyId(),
|
||||
ProjectId: p.GetId(),
|
||||
ApplicationId: createdApp.GetApplicationId(),
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// LoginUser
|
||||
{
|
||||
testName: "when user has no project.application.write permission for application key deletion should return permission error",
|
||||
inputCtx: LoginUserCtx,
|
||||
deletionRequest: func(ttt *testing.T) *application.DeleteApplicationKeyRequest {
|
||||
createdAppKey := createAppKey(ttt, IAMOwnerCtx, instance, p.GetId(), createdApp.GetApplicationId(), time.Now().AddDate(0, 0, 1))
|
||||
|
||||
return &application.DeleteApplicationKeyRequest{
|
||||
KeyId: createdAppKey.GetKeyId(),
|
||||
ProjectId: p.GetId(),
|
||||
ApplicationId: createdApp.GetApplicationId(),
|
||||
}
|
||||
},
|
||||
expectedErrorType: codes.PermissionDenied,
|
||||
},
|
||||
|
||||
// ProjectOwner
|
||||
{
|
||||
testName: "when user is OrgOwner API request should succeed",
|
||||
inputCtx: projectOwnerCtx,
|
||||
deletionRequest: func(ttt *testing.T) *application.DeleteApplicationKeyRequest {
|
||||
createdAppKey := createAppKey(ttt, IAMOwnerCtx, instance, p.GetId(), createdApp.GetApplicationId(), time.Now().AddDate(0, 0, 1))
|
||||
|
||||
return &application.DeleteApplicationKeyRequest{
|
||||
KeyId: createdAppKey.GetKeyId(),
|
||||
ProjectId: p.GetId(),
|
||||
ApplicationId: createdApp.GetApplicationId(),
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
// OrganizationOwner
|
||||
{
|
||||
testName: "when user is OrgOwner application key deletion request should succeed",
|
||||
inputCtx: OrgOwnerCtx,
|
||||
deletionRequest: func(ttt *testing.T) *application.DeleteApplicationKeyRequest {
|
||||
createdAppKey := createAppKey(ttt, IAMOwnerCtx, instance, p.GetId(), createdApp.GetApplicationId(), time.Now().AddDate(0, 0, 1))
|
||||
|
||||
return &application.DeleteApplicationKeyRequest{
|
||||
KeyId: createdAppKey.GetKeyId(),
|
||||
ProjectId: p.GetId(),
|
||||
ApplicationId: createdApp.GetApplicationId(),
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Given
|
||||
deletionReq := tc.deletionRequest(t)
|
||||
|
||||
// When
|
||||
res, err := instance.Client.ApplicationV2.DeleteApplicationKey(tc.inputCtx, deletionReq)
|
||||
|
||||
// Then
|
||||
require.Equal(t, tc.expectedErrorType, status.Code(err))
|
||||
if tc.expectedErrorType == codes.OK {
|
||||
assert.NotEmpty(t, res.GetDeletionDate())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,913 @@
|
||||
//go:build integration
|
||||
|
||||
package app_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/application/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||
)
|
||||
|
||||
func TestGetApplication(t *testing.T) {
|
||||
p, projectOwnerCtx := getProjectAndProjectContext(t, instance, IAMOwnerCtx)
|
||||
|
||||
apiAppName := integration.ApplicationName()
|
||||
createdApiApp, errAPIAppCreation := instance.Client.ApplicationV2.CreateApplication(IAMOwnerCtx, &application.CreateApplicationRequest{
|
||||
ProjectId: p.GetId(),
|
||||
Name: apiAppName,
|
||||
ApplicationType: &application.CreateApplicationRequest_ApiConfiguration{
|
||||
ApiConfiguration: &application.CreateAPIApplicationRequest{
|
||||
AuthMethodType: application.APIAuthMethodType_API_AUTH_METHOD_TYPE_BASIC,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.Nil(t, errAPIAppCreation)
|
||||
|
||||
samlAppName := integration.ApplicationName()
|
||||
createdSAMLApp, errSAMLAppCreation := instance.Client.ApplicationV2.CreateApplication(IAMOwnerCtx, &application.CreateApplicationRequest{
|
||||
ProjectId: p.GetId(),
|
||||
Name: samlAppName,
|
||||
ApplicationType: &application.CreateApplicationRequest_SamlConfiguration{
|
||||
SamlConfiguration: &application.CreateSAMLApplicationRequest{
|
||||
LoginVersion: &application.LoginVersion{Version: &application.LoginVersion_LoginV1{LoginV1: &application.LoginV1{}}},
|
||||
Metadata: &application.CreateSAMLApplicationRequest_MetadataXml{MetadataXml: samlMetadataGen(integration.URL())},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.Nil(t, errSAMLAppCreation)
|
||||
|
||||
oidcAppName := integration.ApplicationName()
|
||||
createdOIDCApp, errOIDCAppCreation := instance.Client.ApplicationV2.CreateApplication(IAMOwnerCtx, &application.CreateApplicationRequest{
|
||||
ProjectId: p.GetId(),
|
||||
Name: oidcAppName,
|
||||
ApplicationType: &application.CreateApplicationRequest_OidcConfiguration{
|
||||
OidcConfiguration: &application.CreateOIDCApplicationRequest{
|
||||
RedirectUris: []string{"http://example.com"},
|
||||
ResponseTypes: []application.OIDCResponseType{application.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE},
|
||||
GrantTypes: []application.OIDCGrantType{application.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE},
|
||||
ApplicationType: application.OIDCApplicationType_OIDC_APP_TYPE_WEB,
|
||||
AuthMethodType: application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC,
|
||||
PostLogoutRedirectUris: []string{"http://example.com/home"},
|
||||
Version: application.OIDCVersion_OIDC_VERSION_1_0,
|
||||
AccessTokenType: application.OIDCTokenType_OIDC_TOKEN_TYPE_JWT,
|
||||
BackChannelLogoutUri: "http://example.com/logout",
|
||||
LoginVersion: &application.LoginVersion{Version: &application.LoginVersion_LoginV2{LoginV2: &application.LoginV2{BaseUri: &baseURI}}},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.Nil(t, errOIDCAppCreation)
|
||||
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *application.GetApplicationRequest
|
||||
inputCtx context.Context
|
||||
|
||||
expectedErrorType codes.Code
|
||||
expectedAppName string
|
||||
expectedAppID string
|
||||
expectedApplicationType string
|
||||
}{
|
||||
{
|
||||
testName: "when unknown application ID should return not found error",
|
||||
inputCtx: IAMOwnerCtx,
|
||||
inputRequest: &application.GetApplicationRequest{
|
||||
ApplicationId: integration.ID(),
|
||||
},
|
||||
|
||||
expectedErrorType: codes.NotFound,
|
||||
},
|
||||
{
|
||||
testName: "when user has no permission should return membership not found error",
|
||||
inputCtx: NoPermissionCtx,
|
||||
inputRequest: &application.GetApplicationRequest{
|
||||
ApplicationId: createdApiApp.GetApplicationId(),
|
||||
},
|
||||
|
||||
expectedErrorType: codes.NotFound,
|
||||
},
|
||||
{
|
||||
testName: "when providing API application ID should return valid API application result",
|
||||
inputCtx: projectOwnerCtx,
|
||||
inputRequest: &application.GetApplicationRequest{
|
||||
ApplicationId: createdApiApp.GetApplicationId(),
|
||||
},
|
||||
|
||||
expectedAppName: apiAppName,
|
||||
expectedAppID: createdApiApp.GetApplicationId(),
|
||||
expectedApplicationType: fmt.Sprintf("%T", &application.Application_ApiConfiguration{}),
|
||||
},
|
||||
{
|
||||
testName: "when providing SAML application ID should return valid SAML application result",
|
||||
inputCtx: IAMOwnerCtx,
|
||||
inputRequest: &application.GetApplicationRequest{
|
||||
ApplicationId: createdSAMLApp.GetApplicationId(),
|
||||
},
|
||||
|
||||
expectedAppName: samlAppName,
|
||||
expectedAppID: createdSAMLApp.GetApplicationId(),
|
||||
expectedApplicationType: fmt.Sprintf("%T", &application.Application_SamlConfiguration{}),
|
||||
},
|
||||
{
|
||||
testName: "when providing OIDC application ID should return valid OIDC application result",
|
||||
inputCtx: IAMOwnerCtx,
|
||||
inputRequest: &application.GetApplicationRequest{
|
||||
ApplicationId: createdOIDCApp.GetApplicationId(),
|
||||
},
|
||||
|
||||
expectedAppName: oidcAppName,
|
||||
expectedAppID: createdOIDCApp.GetApplicationId(),
|
||||
expectedApplicationType: fmt.Sprintf("%T", &application.Application_OidcConfiguration{}),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputCtx, time.Minute)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
// When
|
||||
res, err := instance.Client.ApplicationV2.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.GetApplication().GetApplicationId())
|
||||
assert.Equal(t, tc.expectedAppName, res.GetApplication().GetName())
|
||||
assert.NotZero(t, res.GetApplication().GetCreationDate())
|
||||
assert.NotZero(t, res.GetApplication().GetChangeDate())
|
||||
|
||||
appType := fmt.Sprintf("%T", res.GetApplication().GetConfiguration())
|
||||
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, IAMOwnerCtx, instance, p.GetId())
|
||||
|
||||
createdDeactivatedApiApp, deactivatedApiAppName := createAPIAppWithName(t, IAMOwnerCtx, instance, p.GetId())
|
||||
deactivateApp(t, createdDeactivatedApiApp, p.GetId())
|
||||
|
||||
_, createdSAMLApp, samlAppName := createSAMLAppWithName(t, integration.URL(), p.GetId())
|
||||
|
||||
createdOIDCApp, oidcAppName := createOIDCAppWithName(t, integration.URL(), p.GetId())
|
||||
|
||||
type appWithName struct {
|
||||
app *application.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.GetApplicationId() < b.app.GetApplicationId() {
|
||||
return -1
|
||||
}
|
||||
if a.app.GetApplicationId() > b.app.GetApplicationId() {
|
||||
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 *application.ListApplicationsRequest
|
||||
inputCtx context.Context
|
||||
|
||||
expectedOrderedList []appWithName
|
||||
expectedOrderedKeys func(keys []appWithName) any
|
||||
actualOrderedKeys func(keys []*application.Application) any
|
||||
}{
|
||||
{
|
||||
testName: "when no apps found should return empty list",
|
||||
inputCtx: IAMOwnerCtx,
|
||||
inputRequest: &application.ListApplicationsRequest{
|
||||
Filters: []*application.ApplicationSearchFilter{
|
||||
{
|
||||
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
|
||||
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: "another-id"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
expectedOrderedList: []appWithName{},
|
||||
expectedOrderedKeys: func(keys []appWithName) any { return keys },
|
||||
actualOrderedKeys: func(keys []*application.Application) any { return keys },
|
||||
},
|
||||
{
|
||||
testName: "when user has no read permission should return empty set",
|
||||
inputCtx: NoPermissionCtx,
|
||||
inputRequest: &application.ListApplicationsRequest{
|
||||
Filters: []*application.ApplicationSearchFilter{
|
||||
{
|
||||
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
|
||||
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
expectedOrderedList: []appWithName{},
|
||||
expectedOrderedKeys: func(keys []appWithName) any { return keys },
|
||||
actualOrderedKeys: func(keys []*application.Application) any { return keys },
|
||||
},
|
||||
{
|
||||
testName: "when sorting by name should return apps sorted by name in descending order",
|
||||
inputCtx: IAMOwnerCtx,
|
||||
inputRequest: &application.ListApplicationsRequest{
|
||||
SortingColumn: application.ApplicationSorting_APPLICATION_SORT_BY_NAME,
|
||||
Pagination: &filter.PaginationRequest{Asc: true},
|
||||
Filters: []*application.ApplicationSearchFilter{
|
||||
{
|
||||
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
|
||||
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
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 []*application.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: &application.ListApplicationsRequest{
|
||||
Filters: []*application.ApplicationSearchFilter{
|
||||
{
|
||||
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
|
||||
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
|
||||
},
|
||||
},
|
||||
},
|
||||
SortingColumn: application.ApplicationSorting_APPLICATION_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 []*application.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: &application.ListApplicationsRequest{
|
||||
Filters: []*application.ApplicationSearchFilter{
|
||||
{
|
||||
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
|
||||
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
|
||||
},
|
||||
},
|
||||
},
|
||||
SortingColumn: application.ApplicationSorting_APPLICATION_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.GetApplicationId()
|
||||
}
|
||||
|
||||
return ids
|
||||
},
|
||||
actualOrderedKeys: func(apps []*application.Application) any {
|
||||
ids := make([]string, len(apps))
|
||||
for i, a := range apps {
|
||||
ids[i] = a.GetApplicationId()
|
||||
}
|
||||
|
||||
return ids
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "when sorting by creation date should return apps sorted by creation date in descending order",
|
||||
inputCtx: IAMOwnerCtx,
|
||||
inputRequest: &application.ListApplicationsRequest{
|
||||
Filters: []*application.ApplicationSearchFilter{
|
||||
{
|
||||
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
|
||||
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
|
||||
},
|
||||
},
|
||||
},
|
||||
SortingColumn: application.ApplicationSorting_APPLICATION_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 []*application.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: &application.ListApplicationsRequest{
|
||||
Pagination: &filter.PaginationRequest{Asc: true},
|
||||
Filters: []*application.ApplicationSearchFilter{
|
||||
{
|
||||
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
|
||||
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
|
||||
},
|
||||
},
|
||||
{
|
||||
Filter: &application.ApplicationSearchFilter_StateFilter{
|
||||
StateFilter: application.ApplicationState_APPLICATION_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 []*application.Application) any {
|
||||
creationDates := make([]time.Time, len(apps))
|
||||
for i, a := range apps {
|
||||
creationDates[i] = a.GetCreationDate().AsTime()
|
||||
}
|
||||
|
||||
return creationDates
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "when filtering by application type should return apps of matching type only",
|
||||
inputCtx: IAMOwnerCtx,
|
||||
inputRequest: &application.ListApplicationsRequest{
|
||||
Pagination: &filter.PaginationRequest{Asc: true},
|
||||
Filters: []*application.ApplicationSearchFilter{
|
||||
{
|
||||
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
|
||||
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
|
||||
},
|
||||
},
|
||||
{Filter: &application.ApplicationSearchFilter_TypeFilter{
|
||||
TypeFilter: application.ApplicationType_APPLICATION_TYPE_OIDC,
|
||||
}},
|
||||
},
|
||||
},
|
||||
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 []*application.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, time.Minute)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
// When
|
||||
res, err := instance.Client.ApplicationV2.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.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
p, projectOwnerCtx := getProjectAndProjectContext(t, instancePermissionV2, iamOwnerCtx)
|
||||
_, otherProjectOwnerCtx := getProjectAndProjectContext(t, instancePermissionV2, iamOwnerCtx)
|
||||
|
||||
appName1, appName2, appName3 := integration.ApplicationName(), integration.ApplicationName(), integration.ApplicationName()
|
||||
reqForAPIAppCreation := &application.CreateApplicationRequest_ApiConfiguration{
|
||||
ApiConfiguration: &application.CreateAPIApplicationRequest{AuthMethodType: application.APIAuthMethodType_API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT},
|
||||
}
|
||||
|
||||
app1, appAPIConfigChangeErr := instancePermissionV2.Client.ApplicationV2.CreateApplication(iamOwnerCtx, &application.CreateApplicationRequest{
|
||||
ProjectId: p.GetId(),
|
||||
Name: appName1,
|
||||
ApplicationType: reqForAPIAppCreation,
|
||||
})
|
||||
require.Nil(t, appAPIConfigChangeErr)
|
||||
|
||||
app2, appAPIConfigChangeErr := instancePermissionV2.Client.ApplicationV2.CreateApplication(iamOwnerCtx, &application.CreateApplicationRequest{
|
||||
ProjectId: p.GetId(),
|
||||
Name: appName2,
|
||||
ApplicationType: reqForAPIAppCreation,
|
||||
})
|
||||
require.Nil(t, appAPIConfigChangeErr)
|
||||
|
||||
app3, appAPIConfigChangeErr := instancePermissionV2.Client.ApplicationV2.CreateApplication(iamOwnerCtx, &application.CreateApplicationRequest{
|
||||
ProjectId: p.GetId(),
|
||||
Name: appName3,
|
||||
ApplicationType: reqForAPIAppCreation,
|
||||
})
|
||||
require.Nil(t, appAPIConfigChangeErr)
|
||||
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *application.ListApplicationsRequest
|
||||
inputCtx context.Context
|
||||
|
||||
expectedCode codes.Code
|
||||
expectedAppIDs []string
|
||||
}{
|
||||
{
|
||||
testName: "when user has no read permission should return empty set",
|
||||
inputCtx: instancePermissionV2.WithAuthorizationToken(context.Background(), integration.UserTypeNoPermission),
|
||||
inputRequest: &application.ListApplicationsRequest{
|
||||
Filters: []*application.ApplicationSearchFilter{
|
||||
{
|
||||
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
|
||||
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
expectedAppIDs: []string{},
|
||||
},
|
||||
{
|
||||
testName: "when projectOwner should return full application list",
|
||||
inputCtx: projectOwnerCtx,
|
||||
inputRequest: &application.ListApplicationsRequest{
|
||||
Filters: []*application.ApplicationSearchFilter{
|
||||
{
|
||||
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
|
||||
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
expectedCode: codes.OK,
|
||||
expectedAppIDs: []string{app1.GetApplicationId(), app2.GetApplicationId(), app3.GetApplicationId()},
|
||||
},
|
||||
{
|
||||
testName: "when orgOwner should return full application list",
|
||||
inputCtx: instancePermissionV2.WithAuthorizationToken(context.Background(), integration.UserTypeOrgOwner),
|
||||
inputRequest: &application.ListApplicationsRequest{
|
||||
Filters: []*application.ApplicationSearchFilter{
|
||||
{
|
||||
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
|
||||
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
expectedAppIDs: []string{app1.GetApplicationId(), app2.GetApplicationId(), app3.GetApplicationId()},
|
||||
},
|
||||
{
|
||||
testName: "when iamOwner user should return full application list",
|
||||
inputCtx: iamOwnerCtx,
|
||||
inputRequest: &application.ListApplicationsRequest{
|
||||
Filters: []*application.ApplicationSearchFilter{
|
||||
{
|
||||
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
|
||||
ProjectIdFilter: &application.ProjectIDFilter{ProjectId: p.GetId()},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
expectedAppIDs: []string{app1.GetApplicationId(), app2.GetApplicationId(), app3.GetApplicationId()},
|
||||
},
|
||||
{
|
||||
testName: "when other projectOwner user should return empty list",
|
||||
inputCtx: otherProjectOwnerCtx,
|
||||
inputRequest: &application.ListApplicationsRequest{
|
||||
Filters: []*application.ApplicationSearchFilter{
|
||||
{
|
||||
Filter: &application.ApplicationSearchFilter_ProjectIdFilter{
|
||||
ProjectIdFilter: &application.ProjectIDFilter{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, time.Minute)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
// When
|
||||
res, err := instancePermissionV2.Client.ApplicationV2.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.GetApplicationId())
|
||||
}
|
||||
|
||||
assert.ElementsMatch(ttt, tc.expectedAppIDs, resAppIDs)
|
||||
}
|
||||
}, retryDuration, tick)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetApplicationKey(t *testing.T) {
|
||||
p, projectOwnerCtx := getProjectAndProjectContext(t, instance, IAMOwnerCtx)
|
||||
createdApiApp := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
|
||||
createdAppKey := createAppKey(t, IAMOwnerCtx, instance, p.GetId(), createdApiApp.GetApplicationId(), time.Now().AddDate(0, 0, 1))
|
||||
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *application.GetApplicationKeyRequest
|
||||
inputCtx context.Context
|
||||
|
||||
expectedErrorType codes.Code
|
||||
expectedAppKeyID string
|
||||
}{
|
||||
{
|
||||
testName: "when unknown application ID should return not found error",
|
||||
inputCtx: IAMOwnerCtx,
|
||||
inputRequest: &application.GetApplicationKeyRequest{
|
||||
KeyId: integration.ID(),
|
||||
},
|
||||
|
||||
expectedErrorType: codes.NotFound,
|
||||
},
|
||||
{
|
||||
testName: "when user has no permission should return membership not found error",
|
||||
inputCtx: NoPermissionCtx,
|
||||
inputRequest: &application.GetApplicationKeyRequest{
|
||||
KeyId: createdAppKey.GetKeyId(),
|
||||
},
|
||||
|
||||
expectedErrorType: codes.NotFound,
|
||||
},
|
||||
{
|
||||
testName: "when providing API application ID should return valid API application result",
|
||||
inputCtx: projectOwnerCtx,
|
||||
inputRequest: &application.GetApplicationKeyRequest{
|
||||
KeyId: createdAppKey.GetKeyId(),
|
||||
},
|
||||
|
||||
expectedAppKeyID: createdAppKey.GetKeyId(),
|
||||
},
|
||||
{
|
||||
testName: "when user is OrgOwner should return request key",
|
||||
inputCtx: OrgOwnerCtx,
|
||||
inputRequest: &application.GetApplicationKeyRequest{
|
||||
KeyId: createdAppKey.GetKeyId(),
|
||||
},
|
||||
|
||||
expectedAppKeyID: createdAppKey.GetKeyId(),
|
||||
},
|
||||
{
|
||||
testName: "when user is IAMOwner should return request key",
|
||||
inputCtx: OrgOwnerCtx,
|
||||
inputRequest: &application.GetApplicationKeyRequest{
|
||||
KeyId: createdAppKey.GetKeyId(),
|
||||
},
|
||||
|
||||
expectedAppKeyID: createdAppKey.GetKeyId(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputCtx, time.Minute)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
// When
|
||||
res, err := instance.Client.ApplicationV2.GetApplicationKey(tc.inputCtx, tc.inputRequest)
|
||||
|
||||
// Then
|
||||
require.Equal(t, tc.expectedErrorType, status.Code(err))
|
||||
if tc.expectedErrorType == codes.OK {
|
||||
|
||||
assert.Equal(t, tc.expectedAppKeyID, res.GetKeyId())
|
||||
assert.NotEmpty(t, res.GetCreationDate())
|
||||
assert.NotEmpty(t, res.GetExpirationDate())
|
||||
}
|
||||
}, retryDuration, tick)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListApplicationKeys(t *testing.T) {
|
||||
p, projectOwnerCtx := getProjectAndProjectContext(t, instance, IAMOwnerCtx)
|
||||
|
||||
createdApiApp1 := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
|
||||
createdApiApp2 := createAPIApp(t, IAMOwnerCtx, instance, p.GetId())
|
||||
|
||||
tomorrow := time.Now().AddDate(0, 0, 1)
|
||||
in2Days := tomorrow.AddDate(0, 0, 1)
|
||||
in3Days := in2Days.AddDate(0, 0, 1)
|
||||
|
||||
appKey1 := createAppKey(t, IAMOwnerCtx, instance, p.GetId(), createdApiApp1.GetApplicationId(), in2Days)
|
||||
appKey2 := createAppKey(t, IAMOwnerCtx, instance, p.GetId(), createdApiApp1.GetApplicationId(), in3Days)
|
||||
appKey3 := createAppKey(t, IAMOwnerCtx, instance, p.GetId(), createdApiApp1.GetApplicationId(), tomorrow)
|
||||
appKey4 := createAppKey(t, IAMOwnerCtx, instance, p.GetId(), createdApiApp2.GetApplicationId(), tomorrow)
|
||||
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *application.ListApplicationKeysRequest
|
||||
deps func() (projectID, applicationID, organizationID string)
|
||||
inputCtx context.Context
|
||||
|
||||
expectedErrorType codes.Code
|
||||
expectedAppKeysIDs []string
|
||||
}{
|
||||
{
|
||||
testName: "when sorting by expiration date should return keys sorted by expiration date ascending",
|
||||
inputCtx: LoginUserCtx,
|
||||
inputRequest: &application.ListApplicationKeysRequest{
|
||||
Pagination: &filter.PaginationRequest{Asc: true},
|
||||
SortingColumn: application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_EXPIRATION,
|
||||
Filters: []*application.ApplicationKeySearchFilter{
|
||||
{Filter: &application.ApplicationKeySearchFilter_ProjectIdFilter{
|
||||
ProjectIdFilter: &application.ApplicationKeyProjectIDFilter{ProjectId: p.GetId()},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expectedAppKeysIDs: []string{appKey3.GetKeyId(), appKey4.GetKeyId(), appKey1.GetKeyId(), appKey2.GetKeyId()},
|
||||
},
|
||||
{
|
||||
testName: "when sorting by creation date should return keys sorted by creation date descending",
|
||||
inputCtx: IAMOwnerCtx,
|
||||
inputRequest: &application.ListApplicationKeysRequest{
|
||||
SortingColumn: application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_CREATION_DATE,
|
||||
Filters: []*application.ApplicationKeySearchFilter{
|
||||
{Filter: &application.ApplicationKeySearchFilter_ProjectIdFilter{
|
||||
ProjectIdFilter: &application.ApplicationKeyProjectIDFilter{ProjectId: p.GetId()},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expectedAppKeysIDs: []string{appKey4.GetKeyId(), appKey3.GetKeyId(), appKey2.GetKeyId(), appKey1.GetKeyId()},
|
||||
},
|
||||
{
|
||||
testName: "when filtering by application ID should return keys matching application ID sorted by ID",
|
||||
inputCtx: projectOwnerCtx,
|
||||
inputRequest: &application.ListApplicationKeysRequest{
|
||||
Pagination: &filter.PaginationRequest{Asc: true},
|
||||
Filters: []*application.ApplicationKeySearchFilter{
|
||||
{Filter: &application.ApplicationKeySearchFilter_ApplicationIdFilter{
|
||||
ApplicationIdFilter: &application.ApplicationKeyApplicationIDFilter{ApplicationId: createdApiApp1.GetApplicationId()},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expectedAppKeysIDs: []string{appKey1.GetKeyId(), appKey2.GetKeyId(), appKey3.GetKeyId()},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputCtx, time.Minute)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
// When
|
||||
res, err := instance.Client.ApplicationV2.ListApplicationKeys(tc.inputCtx, tc.inputRequest)
|
||||
|
||||
// Then
|
||||
require.Equal(ttt, tc.expectedErrorType, status.Code(err))
|
||||
if tc.expectedErrorType == codes.OK {
|
||||
require.Len(ttt, res.GetKeys(), len(tc.expectedAppKeysIDs))
|
||||
|
||||
for i, k := range res.GetKeys() {
|
||||
assert.Equal(ttt, tc.expectedAppKeysIDs[i], k.GetKeyId())
|
||||
}
|
||||
}
|
||||
}, retryDuration, tick)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListApplicationKeys_WithPermissionV2(t *testing.T) {
|
||||
ensureFeaturePermissionV2Enabled(t, instancePermissionV2)
|
||||
iamOwnerCtx := instancePermissionV2.WithAuthorizationToken(context.Background(), integration.UserTypeIAMOwner)
|
||||
loginUserCtx := instancePermissionV2.WithAuthorizationToken(context.Background(), integration.UserTypeLogin)
|
||||
p, projectOwnerCtx := getProjectAndProjectContext(t, instancePermissionV2, iamOwnerCtx)
|
||||
|
||||
createdApiApp1 := createAPIApp(t, iamOwnerCtx, instancePermissionV2, p.GetId())
|
||||
createdApiApp2 := createAPIApp(t, iamOwnerCtx, instancePermissionV2, p.GetId())
|
||||
|
||||
tomorrow := time.Now().AddDate(0, 0, 1)
|
||||
in2Days := tomorrow.AddDate(0, 0, 1)
|
||||
in3Days := in2Days.AddDate(0, 0, 1)
|
||||
|
||||
appKey1 := createAppKey(t, iamOwnerCtx, instancePermissionV2, p.GetId(), createdApiApp1.GetApplicationId(), in2Days)
|
||||
appKey2 := createAppKey(t, iamOwnerCtx, instancePermissionV2, p.GetId(), createdApiApp1.GetApplicationId(), in3Days)
|
||||
appKey3 := createAppKey(t, iamOwnerCtx, instancePermissionV2, p.GetId(), createdApiApp1.GetApplicationId(), tomorrow)
|
||||
appKey4 := createAppKey(t, iamOwnerCtx, instancePermissionV2, p.GetId(), createdApiApp2.GetApplicationId(), tomorrow)
|
||||
|
||||
t.Parallel()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputRequest *application.ListApplicationKeysRequest
|
||||
deps func() (projectID, applicationID, organizationID string)
|
||||
inputCtx context.Context
|
||||
|
||||
expectedErrorType codes.Code
|
||||
expectedAppKeysIDs []string
|
||||
}{
|
||||
{
|
||||
testName: "when sorting by expiration date should return keys sorted by expiration date ascending",
|
||||
inputCtx: loginUserCtx,
|
||||
inputRequest: &application.ListApplicationKeysRequest{
|
||||
Pagination: &filter.PaginationRequest{Asc: true},
|
||||
SortingColumn: application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_EXPIRATION,
|
||||
},
|
||||
expectedAppKeysIDs: []string{appKey3.GetKeyId(), appKey4.GetKeyId(), appKey1.GetKeyId(), appKey2.GetKeyId()},
|
||||
},
|
||||
{
|
||||
testName: "when sorting by creation date should return keys sorted by creation date descending",
|
||||
inputCtx: iamOwnerCtx,
|
||||
inputRequest: &application.ListApplicationKeysRequest{
|
||||
SortingColumn: application.ApplicationKeysSorting_APPLICATION_KEYS_SORT_BY_CREATION_DATE,
|
||||
},
|
||||
expectedAppKeysIDs: []string{appKey4.GetKeyId(), appKey3.GetKeyId(), appKey2.GetKeyId(), appKey1.GetKeyId()},
|
||||
},
|
||||
{
|
||||
testName: "when filtering by application ID should return keys matching application ID sorted by ID",
|
||||
inputCtx: projectOwnerCtx,
|
||||
inputRequest: &application.ListApplicationKeysRequest{
|
||||
Pagination: &filter.PaginationRequest{Asc: true},
|
||||
Filters: []*application.ApplicationKeySearchFilter{
|
||||
{Filter: &application.ApplicationKeySearchFilter_ApplicationIdFilter{
|
||||
ApplicationIdFilter: &application.ApplicationKeyApplicationIDFilter{ApplicationId: createdApiApp1.GetApplicationId()},
|
||||
}},
|
||||
},
|
||||
},
|
||||
expectedAppKeysIDs: []string{appKey1.GetKeyId(), appKey2.GetKeyId(), appKey3.GetKeyId()},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
// t.Parallel()
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tc.inputCtx, time.Minute)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
// When
|
||||
res, err := instancePermissionV2.Client.ApplicationV2.ListApplicationKeys(tc.inputCtx, tc.inputRequest)
|
||||
|
||||
// Then
|
||||
require.Equal(ttt, tc.expectedErrorType, status.Code(err))
|
||||
if tc.expectedErrorType == codes.OK {
|
||||
require.Len(ttt, res.GetKeys(), len(tc.expectedAppKeysIDs))
|
||||
|
||||
for i, k := range res.GetKeys() {
|
||||
assert.Equal(ttt, tc.expectedAppKeysIDs[i], k.GetKeyId())
|
||||
}
|
||||
}
|
||||
}, retryDuration, tick)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
//go:build integration
|
||||
|
||||
package app_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/application/v2"
|
||||
"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(), integration.ProjectName(), 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, *application.CreateApplicationResponse, string) {
|
||||
samlMetas := samlMetadataGen(integration.URL())
|
||||
appName := integration.ApplicationName()
|
||||
|
||||
appForSAMLConfigChange, appSAMLConfigChangeErr := instance.Client.ApplicationV2.CreateApplication(IAMOwnerCtx, &application.CreateApplicationRequest{
|
||||
ProjectId: projectID,
|
||||
Name: appName,
|
||||
ApplicationType: &application.CreateApplicationRequest_SamlConfiguration{
|
||||
SamlConfiguration: &application.CreateSAMLApplicationRequest{
|
||||
Metadata: &application.CreateSAMLApplicationRequest_MetadataXml{
|
||||
MetadataXml: samlMetas,
|
||||
},
|
||||
LoginVersion: &application.LoginVersion{
|
||||
Version: &application.LoginVersion_LoginV2{
|
||||
LoginV2: &application.LoginV2{
|
||||
BaseUri: &baseURI,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.Nil(t, appSAMLConfigChangeErr)
|
||||
|
||||
return samlMetas, appForSAMLConfigChange, appName
|
||||
}
|
||||
|
||||
func createSAMLApp(t *testing.T, baseURI, projectID string) ([]byte, *application.CreateApplicationResponse) {
|
||||
metas, app, _ := createSAMLAppWithName(t, baseURI, projectID)
|
||||
return metas, app
|
||||
}
|
||||
|
||||
func createOIDCAppWithName(t *testing.T, baseURI, projectID string) (*application.CreateApplicationResponse, string) {
|
||||
appName := integration.ApplicationName()
|
||||
|
||||
appForOIDCConfigChange, appOIDCConfigChangeErr := instance.Client.ApplicationV2.CreateApplication(IAMOwnerCtx, &application.CreateApplicationRequest{
|
||||
ProjectId: projectID,
|
||||
Name: appName,
|
||||
ApplicationType: &application.CreateApplicationRequest_OidcConfiguration{
|
||||
OidcConfiguration: &application.CreateOIDCApplicationRequest{
|
||||
RedirectUris: []string{"http://example.com"},
|
||||
ResponseTypes: []application.OIDCResponseType{application.OIDCResponseType_OIDC_RESPONSE_TYPE_CODE},
|
||||
GrantTypes: []application.OIDCGrantType{application.OIDCGrantType_OIDC_GRANT_TYPE_AUTHORIZATION_CODE},
|
||||
ApplicationType: application.OIDCApplicationType_OIDC_APP_TYPE_WEB,
|
||||
AuthMethodType: application.OIDCAuthMethodType_OIDC_AUTH_METHOD_TYPE_BASIC,
|
||||
PostLogoutRedirectUris: []string{"http://example.com/home"},
|
||||
Version: application.OIDCVersion_OIDC_VERSION_1_0,
|
||||
AccessTokenType: application.OIDCTokenType_OIDC_TOKEN_TYPE_JWT,
|
||||
BackChannelLogoutUri: "http://example.com/logout",
|
||||
LoginVersion: &application.LoginVersion{
|
||||
Version: &application.LoginVersion_LoginV2{
|
||||
LoginV2: &application.LoginV2{
|
||||
BaseUri: &baseURI,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.Nil(t, appOIDCConfigChangeErr)
|
||||
|
||||
return appForOIDCConfigChange, appName
|
||||
}
|
||||
|
||||
func createOIDCApp(t *testing.T, baseURI, projctID string) *application.CreateApplicationResponse {
|
||||
app, _ := createOIDCAppWithName(t, baseURI, projctID)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
func createAPIAppWithName(t *testing.T, ctx context.Context, inst *integration.Instance, projectID string) (*application.CreateApplicationResponse, string) {
|
||||
appName := integration.ApplicationName()
|
||||
|
||||
reqForAPIAppCreation := &application.CreateApplicationRequest_ApiConfiguration{
|
||||
ApiConfiguration: &application.CreateAPIApplicationRequest{AuthMethodType: application.APIAuthMethodType_API_AUTH_METHOD_TYPE_PRIVATE_KEY_JWT},
|
||||
}
|
||||
|
||||
appForAPIConfigChange, appAPIConfigChangeErr := inst.Client.ApplicationV2.CreateApplication(ctx, &application.CreateApplicationRequest{
|
||||
ProjectId: projectID,
|
||||
Name: appName,
|
||||
ApplicationType: reqForAPIAppCreation,
|
||||
})
|
||||
require.Nil(t, appAPIConfigChangeErr)
|
||||
|
||||
return appForAPIConfigChange, appName
|
||||
}
|
||||
|
||||
func createAPIApp(t *testing.T, ctx context.Context, inst *integration.Instance, projectID string) *application.CreateApplicationResponse {
|
||||
res, _ := createAPIAppWithName(t, ctx, inst, projectID)
|
||||
return res
|
||||
}
|
||||
|
||||
func deactivateApp(t *testing.T, appToDeactivate *application.CreateApplicationResponse, projectID string) {
|
||||
_, appDeactivateErr := instance.Client.ApplicationV2.DeactivateApplication(IAMOwnerCtx, &application.DeactivateApplicationRequest{
|
||||
ProjectId: projectID,
|
||||
ApplicationId: appToDeactivate.GetApplicationId(),
|
||||
})
|
||||
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")
|
||||
}
|
||||
|
||||
func createAppKey(t *testing.T, ctx context.Context, inst *integration.Instance, projectID, appID string, expirationDate time.Time) *application.CreateApplicationKeyResponse {
|
||||
res, err := inst.Client.ApplicationV2.CreateApplicationKey(ctx,
|
||||
&application.CreateApplicationKeyRequest{
|
||||
ApplicationId: appID,
|
||||
ProjectId: projectID,
|
||||
ExpirationDate: timestamppb.New(expirationDate.UTC()),
|
||||
},
|
||||
)
|
||||
|
||||
require.Nil(t, err)
|
||||
|
||||
return res
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/application/v2/convert"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/filter/v2"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/application/v2"
|
||||
)
|
||||
|
||||
func (s *Server) GetApplication(ctx context.Context, req *connect.Request[application.GetApplicationRequest]) (*connect.Response[application.GetApplicationResponse], error) {
|
||||
res, err := s.query.AppByIDWithPermission(ctx, req.Msg.GetApplicationId(), false, s.checkPermission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return connect.NewResponse(&application.GetApplicationResponse{
|
||||
Application: convert.AppToPb(res),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) ListApplications(ctx context.Context, req *connect.Request[application.ListApplicationsRequest]) (*connect.Response[application.ListApplicationsResponse], error) {
|
||||
queries, err := convert.ListApplicationsRequestToModel(s.systemDefaults, req.Msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := s.query.SearchApps(ctx, queries, s.checkPermission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return connect.NewResponse(&application.ListApplicationsResponse{
|
||||
Applications: convert.AppsToPb(res.Apps),
|
||||
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, res.SearchResponse),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) GetApplicationKey(ctx context.Context, req *connect.Request[application.GetApplicationKeyRequest]) (*connect.Response[application.GetApplicationKeyResponse], error) {
|
||||
key, err := s.query.GetAuthNKeyByIDWithPermission(ctx, true, strings.TrimSpace(req.Msg.GetKeyId()), s.checkPermission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return connect.NewResponse(&application.GetApplicationKeyResponse{
|
||||
KeyId: key.ID,
|
||||
CreationDate: timestamppb.New(key.CreationDate),
|
||||
ExpirationDate: timestamppb.New(key.Expiration),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (s *Server) ListApplicationKeys(ctx context.Context, req *connect.Request[application.ListApplicationKeysRequest]) (*connect.Response[application.ListApplicationKeysResponse], error) {
|
||||
queries, err := convert.ListApplicationKeysRequestToDomain(s.systemDefaults, req.Msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := s.query.SearchAuthNKeys(ctx, queries, query.JoinFilterUnspecified, s.checkPermission)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return connect.NewResponse(&application.ListApplicationKeysResponse{
|
||||
Keys: convert.ApplicationKeysToPb(res.AuthNKeys),
|
||||
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, res.SearchResponse),
|
||||
}), nil
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"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"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/application/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/application/v2/applicationconnect"
|
||||
)
|
||||
|
||||
var _ applicationconnect.ApplicationServiceHandler = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
command *command.Commands
|
||||
query *query.Queries
|
||||
systemDefaults systemdefaults.SystemDefaults
|
||||
checkPermission domain.PermissionCheck
|
||||
}
|
||||
|
||||
func CreateServer(
|
||||
systemDefaults systemdefaults.SystemDefaults,
|
||||
command *command.Commands,
|
||||
query *query.Queries,
|
||||
checkPermission domain.PermissionCheck,
|
||||
) *Server {
|
||||
return &Server{
|
||||
command: command,
|
||||
query: query,
|
||||
checkPermission: checkPermission,
|
||||
systemDefaults: systemDefaults,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) RegisterConnectServer(interceptors ...connect.Interceptor) (string, http.Handler) {
|
||||
return applicationconnect.NewApplicationServiceHandler(s, connect.WithInterceptors(interceptors...))
|
||||
}
|
||||
|
||||
func (s *Server) FileDescriptor() protoreflect.FileDescriptor {
|
||||
return application.File_zitadel_application_v2_application_service_proto
|
||||
}
|
||||
|
||||
func (s *Server) AppName() string {
|
||||
return application.ApplicationService_ServiceDesc.ServiceName
|
||||
}
|
||||
|
||||
func (s *Server) MethodPrefix() string {
|
||||
return application.ApplicationService_ServiceDesc.ServiceName
|
||||
}
|
||||
|
||||
func (s *Server) AuthMethods() authz.MethodMapping {
|
||||
return application.ApplicationService_AuthMethods
|
||||
}
|
||||
Reference in New Issue
Block a user