mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 00:57:33 +00:00
chore!: Introduce ZITADEL v3 (#9645)
This PR summarizes multiple changes specifically only available with ZITADEL v3: - feat: Web Keys management (https://github.com/zitadel/zitadel/pull/9526) - fix(cmd): ensure proper working of mirror (https://github.com/zitadel/zitadel/pull/9509) - feat(Authz): system user support for permission check v2 (https://github.com/zitadel/zitadel/pull/9640) - chore(license): change from Apache to AGPL (https://github.com/zitadel/zitadel/pull/9597) - feat(console): list v2 sessions (https://github.com/zitadel/zitadel/pull/9539) - fix(console): add loginV2 feature flag (https://github.com/zitadel/zitadel/pull/9682) - fix(feature flags): allow reading "own" flags (https://github.com/zitadel/zitadel/pull/9649) - feat(console): add Actions V2 UI (https://github.com/zitadel/zitadel/pull/9591) BREAKING CHANGE - feat(webkey): migrate to v2beta API (https://github.com/zitadel/zitadel/pull/9445) - chore!: remove CockroachDB Support (https://github.com/zitadel/zitadel/pull/9444) - feat(actions): migrate to v2beta API (https://github.com/zitadel/zitadel/pull/9489) --------- Co-authored-by: Livio Spring <livio.a@gmail.com> Co-authored-by: Stefan Benz <46600784+stebenz@users.noreply.github.com> Co-authored-by: Silvan <27845747+adlerhurst@users.noreply.github.com> Co-authored-by: Ramon <mail@conblem.me> Co-authored-by: Elio Bischof <elio@zitadel.com> Co-authored-by: Kenta Yamaguchi <56732734+KEY60228@users.noreply.github.com> Co-authored-by: Harsha Reddy <harsha.reddy@klaviyo.com> Co-authored-by: Livio Spring <livio@zitadel.com> Co-authored-by: Max Peintner <max@caos.ch> Co-authored-by: Iraq <66622793+kkrime@users.noreply.github.com> Co-authored-by: Florian Forster <florian@zitadel.com> Co-authored-by: Tim Möhlmann <tim+github@zitadel.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Max Peintner <peintnerm@gmail.com>
This commit is contained in:
@@ -15,7 +15,7 @@ import (
|
||||
healthpb "google.golang.org/grpc/health/grpc_health_v1"
|
||||
"google.golang.org/grpc/reflection"
|
||||
|
||||
internal_authz "github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
||||
http_util "github.com/zitadel/zitadel/internal/api/http"
|
||||
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
|
||||
@@ -29,7 +29,7 @@ import (
|
||||
type API struct {
|
||||
port uint16
|
||||
grpcServer *grpc.Server
|
||||
verifier internal_authz.APITokenVerifier
|
||||
verifier authz.APITokenVerifier
|
||||
health healthCheck
|
||||
router *mux.Router
|
||||
hostHeaders []string
|
||||
@@ -72,8 +72,9 @@ func New(
|
||||
port uint16,
|
||||
router *mux.Router,
|
||||
queries *query.Queries,
|
||||
verifier internal_authz.APITokenVerifier,
|
||||
authZ internal_authz.Config,
|
||||
verifier authz.APITokenVerifier,
|
||||
systemAuthz authz.Config,
|
||||
authZ authz.Config,
|
||||
tlsConfig *tls.Config,
|
||||
externalDomain string,
|
||||
hostHeaders []string,
|
||||
@@ -89,7 +90,7 @@ func New(
|
||||
hostHeaders: hostHeaders,
|
||||
}
|
||||
|
||||
api.grpcServer = server.CreateServer(api.verifier, authZ, queries, externalDomain, tlsConfig, accessInterceptor.AccessService())
|
||||
api.grpcServer = server.CreateServer(api.verifier, systemAuthz, authZ, queries, externalDomain, tlsConfig, accessInterceptor.AccessService())
|
||||
api.grpcGateway, err = server.CreateGateway(ctx, port, hostHeaders, accessInterceptor, tlsConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@@ -94,13 +94,13 @@ func DefaultErrorHandler(translator *i18n.Translator) func(w http.ResponseWriter
|
||||
}
|
||||
}
|
||||
|
||||
func NewHandler(commands *command.Commands, verifier authz.APITokenVerifier, authConfig authz.Config, idGenerator id.Generator, storage static.Storage, queries *query.Queries, callDurationInterceptor, instanceInterceptor, assetCacheInterceptor, accessInterceptor func(handler http.Handler) http.Handler) http.Handler {
|
||||
func NewHandler(commands *command.Commands, verifier authz.APITokenVerifier, systemAuthCOnfig authz.Config, authConfig authz.Config, idGenerator id.Generator, storage static.Storage, queries *query.Queries, callDurationInterceptor, instanceInterceptor, assetCacheInterceptor, accessInterceptor func(handler http.Handler) http.Handler) http.Handler {
|
||||
translator, err := i18n.NewZitadelTranslator(language.English)
|
||||
logging.OnError(err).Panic("unable to get translator")
|
||||
h := &Handler{
|
||||
commands: commands,
|
||||
errorHandler: DefaultErrorHandler(translator),
|
||||
authInterceptor: http_mw.AuthorizationInterceptor(verifier, authConfig),
|
||||
authInterceptor: http_mw.AuthorizationInterceptor(verifier, systemAuthCOnfig, authConfig),
|
||||
idGenerator: idGenerator,
|
||||
storage: storage,
|
||||
query: queries,
|
||||
@@ -129,8 +129,10 @@ func (l *publicFileDownloader) ResourceOwner(_ context.Context, ownerPath string
|
||||
return ownerPath
|
||||
}
|
||||
|
||||
const maxMemory = 2 << 20
|
||||
const paramFile = "file"
|
||||
const (
|
||||
maxMemory = 2 << 20
|
||||
paramFile = "file"
|
||||
)
|
||||
|
||||
func UploadHandleFunc(s AssetsService, uploader Uploader) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
|
@@ -4,8 +4,11 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
@@ -16,10 +19,10 @@ const (
|
||||
|
||||
// CheckUserAuthorization verifies that:
|
||||
// - the token is active,
|
||||
// - the organisation (**either** provided by ID or verified domain) exists
|
||||
// - the organization (**either** provided by ID or verified domain) exists
|
||||
// - the user is permitted to call the requested endpoint (permission option in proto)
|
||||
// it will pass the [CtxData] and permission of the user into the ctx [context.Context]
|
||||
func CheckUserAuthorization(ctx context.Context, req interface{}, token, orgID, orgDomain string, verifier APITokenVerifier, authConfig Config, requiredAuthOption Option, method string) (ctxSetter func(context.Context) context.Context, err error) {
|
||||
func CheckUserAuthorization(ctx context.Context, req interface{}, token, orgID, orgDomain string, verifier APITokenVerifier, systemRolePermissionMapping []RoleMapping, rolePermissionMapping []RoleMapping, requiredAuthOption Option, method string) (ctxSetter func(context.Context) context.Context, err error) {
|
||||
ctx, span := tracing.NewServerInterceptorSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -30,11 +33,12 @@ func CheckUserAuthorization(ctx context.Context, req interface{}, token, orgID,
|
||||
|
||||
if requiredAuthOption.Permission == authenticated {
|
||||
return func(parent context.Context) context.Context {
|
||||
parent = addGetSystemUserRolesToCtx(parent, systemRolePermissionMapping, ctxData)
|
||||
return context.WithValue(parent, dataKey, ctxData)
|
||||
}, nil
|
||||
}
|
||||
|
||||
requestedPermissions, allPermissions, err := getUserPermissions(ctx, verifier, requiredAuthOption.Permission, authConfig.RolePermissionMappings, ctxData, ctxData.OrgID)
|
||||
requestedPermissions, allPermissions, err := getUserPermissions(ctx, verifier, requiredAuthOption.Permission, systemRolePermissionMapping, rolePermissionMapping, ctxData, ctxData.OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -50,6 +54,7 @@ func CheckUserAuthorization(ctx context.Context, req interface{}, token, orgID,
|
||||
parent = context.WithValue(parent, dataKey, ctxData)
|
||||
parent = context.WithValue(parent, allPermissionsKey, allPermissions)
|
||||
parent = context.WithValue(parent, requestPermissionsKey, requestedPermissions)
|
||||
parent = addGetSystemUserRolesToCtx(parent, systemRolePermissionMapping, ctxData)
|
||||
return parent
|
||||
}, nil
|
||||
}
|
||||
@@ -125,3 +130,43 @@ func GetAllPermissionCtxIDs(perms []string) []string {
|
||||
}
|
||||
return ctxIDs
|
||||
}
|
||||
|
||||
type SystemUserPermissionsDBQuery struct {
|
||||
MemberType string `json:"member_type"`
|
||||
AggregateID string `json:"aggregate_id"`
|
||||
ObjectID string `json:"object_id"`
|
||||
Permissions []string `json:"permissions"`
|
||||
}
|
||||
|
||||
func addGetSystemUserRolesToCtx(ctx context.Context, systemUserRoleMap []RoleMapping, ctxData CtxData) context.Context {
|
||||
if len(ctxData.SystemMemberships) == 0 {
|
||||
return ctx
|
||||
}
|
||||
systemUserPermissions := make([]SystemUserPermissionsDBQuery, len(ctxData.SystemMemberships))
|
||||
for i, systemPerm := range ctxData.SystemMemberships {
|
||||
permissions := make([]string, 0, len(systemPerm.Roles))
|
||||
for _, role := range systemPerm.Roles {
|
||||
permissions = append(permissions, getPermissionsFromRole(systemUserRoleMap, role)...)
|
||||
}
|
||||
slices.Sort(permissions)
|
||||
permissions = slices.Compact(permissions)
|
||||
|
||||
systemUserPermissions[i].MemberType = systemPerm.MemberType.String()
|
||||
systemUserPermissions[i].AggregateID = systemPerm.AggregateID
|
||||
systemUserPermissions[i].Permissions = permissions
|
||||
}
|
||||
return context.WithValue(ctx, systemUserRolesKey, systemUserPermissions)
|
||||
}
|
||||
|
||||
func GetSystemUserPermissions(ctx context.Context) []SystemUserPermissionsDBQuery {
|
||||
getSystemUserRolesFuncValue := ctx.Value(systemUserRolesKey)
|
||||
if getSystemUserRolesFuncValue == nil {
|
||||
return nil
|
||||
}
|
||||
systemUserRoles, ok := getSystemUserRolesFuncValue.([]SystemUserPermissionsDBQuery)
|
||||
if !ok {
|
||||
logging.WithFields("Authz").Error("unable to cast []SystemUserPermissionsDBQuery")
|
||||
return nil
|
||||
}
|
||||
return systemUserRoles
|
||||
}
|
||||
|
@@ -22,6 +22,7 @@ const (
|
||||
dataKey key = 2
|
||||
allPermissionsKey key = 3
|
||||
instanceKey key = 4
|
||||
systemUserRolesKey key = 5
|
||||
)
|
||||
|
||||
type CtxData struct {
|
||||
@@ -50,7 +51,8 @@ type Memberships []*Membership
|
||||
type Membership struct {
|
||||
MemberType MemberType
|
||||
AggregateID string
|
||||
//ObjectID differs from aggregate id if object is sub of an aggregate
|
||||
InstanceID string
|
||||
// ObjectID differs from aggregate id if object is sub of an aggregate
|
||||
ObjectID string
|
||||
|
||||
Roles []string
|
||||
|
@@ -7,8 +7,8 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
func CheckPermission(ctx context.Context, resolver MembershipsResolver, roleMappings []RoleMapping, permission, orgID, resourceID string) (err error) {
|
||||
requestedPermissions, _, err := getUserPermissions(ctx, resolver, permission, roleMappings, GetCtxData(ctx), orgID)
|
||||
func CheckPermission(ctx context.Context, resolver MembershipsResolver, systemUserRoleMapping []RoleMapping, roleMappings []RoleMapping, permission, orgID, resourceID string) (err error) {
|
||||
requestedPermissions, _, err := getUserPermissions(ctx, resolver, permission, systemUserRoleMapping, roleMappings, GetCtxData(ctx), orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -22,7 +22,7 @@ func CheckPermission(ctx context.Context, resolver MembershipsResolver, roleMapp
|
||||
|
||||
// getUserPermissions retrieves the memberships of the authenticated user (on instance and provided organisation level),
|
||||
// and maps them to permissions. It will return the requested permission(s) and all other granted permissions separately.
|
||||
func getUserPermissions(ctx context.Context, resolver MembershipsResolver, requiredPerm string, roleMappings []RoleMapping, ctxData CtxData, orgID string) (requestedPermissions, allPermissions []string, err error) {
|
||||
func getUserPermissions(ctx context.Context, resolver MembershipsResolver, requiredPerm string, systemUserRoleMappings []RoleMapping, roleMappings []RoleMapping, ctxData CtxData, orgID string) (requestedPermissions, allPermissions []string, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
@@ -31,7 +31,7 @@ func getUserPermissions(ctx context.Context, resolver MembershipsResolver, requi
|
||||
}
|
||||
|
||||
if ctxData.SystemMemberships != nil {
|
||||
requestedPermissions, allPermissions = mapMembershipsToPermissions(requiredPerm, ctxData.SystemMemberships, roleMappings)
|
||||
requestedPermissions, allPermissions = mapMembershipsToPermissions(requiredPerm, ctxData.SystemMemberships, systemUserRoleMappings)
|
||||
return requestedPermissions, allPermissions, nil
|
||||
}
|
||||
|
||||
|
@@ -120,7 +120,7 @@ func Test_GetUserPermissions(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, perms, err := getUserPermissions(context.Background(), tt.args.membershipsResolver, tt.args.requiredPerm, tt.args.authConfig.RolePermissionMappings, tt.args.ctxData, tt.args.ctxData.OrgID)
|
||||
_, perms, err := getUserPermissions(context.Background(), tt.args.membershipsResolver, tt.args.requiredPerm, nil, tt.args.authConfig.RolePermissionMappings, tt.args.ctxData, tt.args.ctxData.OrgID)
|
||||
|
||||
if tt.wantErr && err == nil {
|
||||
t.Errorf("got wrong result, should get err: actual: %v ", err)
|
||||
|
@@ -3,21 +3,21 @@ package action
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
resource_object "github.com/zitadel/zitadel/internal/api/grpc/resources/object/v3alpha"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/repository/execution"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta"
|
||||
)
|
||||
|
||||
func (s *Server) SetExecution(ctx context.Context, req *action.SetExecutionRequest) (*action.SetExecutionResponse, error) {
|
||||
if err := checkActionsEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reqTargets := req.GetExecution().GetTargets()
|
||||
reqTargets := req.GetTargets()
|
||||
targets := make([]*execution.Target, len(reqTargets))
|
||||
for i, target := range reqTargets {
|
||||
switch t := target.GetType().(type) {
|
||||
@@ -56,7 +56,7 @@ func (s *Server) SetExecution(ctx context.Context, req *action.SetExecutionReque
|
||||
return nil, err
|
||||
}
|
||||
return &action.SetExecutionResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID),
|
||||
SetDate: timestamppb.New(details.EventDate),
|
||||
}, nil
|
||||
}
|
||||
|
@@ -5,12 +5,8 @@ package action_test
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -32,21 +28,17 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/app"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/management"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/metadata"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||
resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha"
|
||||
saml_pb "github.com/zitadel/zitadel/pkg/grpc/saml/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/session/v2"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
redirectURI = "https://callback"
|
||||
logoutRedirectURI = "https://logged-out"
|
||||
redirectURIImplicit = "http://localhost:9999/callback"
|
||||
)
|
||||
|
||||
@@ -58,13 +50,12 @@ func TestServer_ExecutionTarget(t *testing.T) {
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
fullMethod := "/zitadel.resources.action.v3alpha.ZITADELActions/GetTarget"
|
||||
fullMethod := action.ActionService_GetTarget_FullMethodName
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
dep func(context.Context, *action.GetTargetRequest, *action.GetTargetResponse) (func(), error)
|
||||
dep func(context.Context, *action.GetTargetRequest, *action.GetTargetResponse) (closeF func(), calledF func() bool)
|
||||
clean func(context.Context)
|
||||
req *action.GetTargetRequest
|
||||
want *action.GetTargetResponse
|
||||
@@ -73,7 +64,7 @@ func TestServer_ExecutionTarget(t *testing.T) {
|
||||
{
|
||||
name: "GetTarget, request and response, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) (func(), error) {
|
||||
dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) (func(), func() bool) {
|
||||
|
||||
orgID := instance.DefaultOrg.Id
|
||||
projectID := ""
|
||||
@@ -87,50 +78,55 @@ func TestServer_ExecutionTarget(t *testing.T) {
|
||||
|
||||
// request received by target
|
||||
wantRequest := &middleware.ContextInfoRequest{FullMethod: fullMethod, InstanceID: instance.ID(), OrgID: orgID, ProjectID: projectID, UserID: userID, Request: request}
|
||||
changedRequest := &action.GetTargetRequest{Id: targetCreated.GetDetails().GetId()}
|
||||
changedRequest := &action.GetTargetRequest{Id: targetCreated.GetId()}
|
||||
// replace original request with different targetID
|
||||
urlRequest, closeRequest := testServerCall(wantRequest, 0, http.StatusOK, changedRequest)
|
||||
urlRequest, closeRequest, calledRequest, _ := integration.TestServerCall(wantRequest, 0, http.StatusOK, changedRequest)
|
||||
|
||||
targetRequest := waitForTarget(ctx, t, instance, urlRequest, domain.TargetTypeCall, false)
|
||||
|
||||
waitForExecutionOnCondition(ctx, t, instance, conditionRequestFullMethod(fullMethod), executionTargetsSingleTarget(targetRequest.GetDetails().GetId()))
|
||||
waitForExecutionOnCondition(ctx, t, instance, conditionRequestFullMethod(fullMethod), executionTargetsSingleTarget(targetRequest.GetId()))
|
||||
|
||||
// expected response from the GetTarget
|
||||
expectedResponse := &action.GetTargetResponse{
|
||||
Target: &action.GetTarget{
|
||||
Config: &action.Target{
|
||||
Name: targetCreatedName,
|
||||
Endpoint: targetCreatedURL,
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
Details: targetCreated.GetDetails(),
|
||||
},
|
||||
}
|
||||
// has to be set separately because of the pointers
|
||||
response.Target = &action.GetTarget{
|
||||
Details: targetCreated.GetDetails(),
|
||||
Config: &action.Target{
|
||||
Name: targetCreatedName,
|
||||
Target: &action.Target{
|
||||
Id: targetCreated.GetId(),
|
||||
CreationDate: targetCreated.GetCreationDate(),
|
||||
ChangeDate: targetCreated.GetCreationDate(),
|
||||
Name: targetCreatedName,
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
RestCall: &action.RESTCall{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
Endpoint: targetCreatedURL,
|
||||
Timeout: durationpb.New(5 * time.Second),
|
||||
Endpoint: targetCreatedURL,
|
||||
SigningKey: targetCreated.GetSigningKey(),
|
||||
},
|
||||
}
|
||||
// has to be set separately because of the pointers
|
||||
response.Target = &action.Target{
|
||||
Id: targetCreated.GetId(),
|
||||
CreationDate: targetCreated.GetCreationDate(),
|
||||
ChangeDate: targetCreated.GetCreationDate(),
|
||||
Name: targetCreatedName,
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.RESTCall{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(5 * time.Second),
|
||||
Endpoint: targetCreatedURL,
|
||||
SigningKey: targetCreated.GetSigningKey(),
|
||||
}
|
||||
|
||||
// content for partial update
|
||||
changedResponse := &action.GetTargetResponse{
|
||||
Target: &action.GetTarget{
|
||||
Details: &resource_object.Details{
|
||||
Id: targetCreated.GetDetails().GetId(),
|
||||
Target: &action.Target{
|
||||
Id: targetCreated.GetId(),
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.RESTCall{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -146,14 +142,22 @@ func TestServer_ExecutionTarget(t *testing.T) {
|
||||
Response: expectedResponse,
|
||||
}
|
||||
// after request with different targetID, return changed response
|
||||
targetResponseURL, closeResponse := testServerCall(wantResponse, 0, http.StatusOK, changedResponse)
|
||||
targetResponseURL, closeResponse, calledResponse, _ := integration.TestServerCall(wantResponse, 0, http.StatusOK, changedResponse)
|
||||
|
||||
targetResponse := waitForTarget(ctx, t, instance, targetResponseURL, domain.TargetTypeCall, false)
|
||||
waitForExecutionOnCondition(ctx, t, instance, conditionResponseFullMethod(fullMethod), executionTargetsSingleTarget(targetResponse.GetDetails().GetId()))
|
||||
waitForExecutionOnCondition(ctx, t, instance, conditionResponseFullMethod(fullMethod), executionTargetsSingleTarget(targetResponse.GetId()))
|
||||
return func() {
|
||||
closeRequest()
|
||||
closeResponse()
|
||||
}, nil
|
||||
closeRequest()
|
||||
closeResponse()
|
||||
}, func() bool {
|
||||
if calledRequest() != 1 {
|
||||
return false
|
||||
}
|
||||
if calledResponse() != 1 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
},
|
||||
clean: func(ctx context.Context) {
|
||||
instance.DeleteExecution(ctx, t, conditionRequestFullMethod(fullMethod))
|
||||
@@ -163,38 +167,32 @@ func TestServer_ExecutionTarget(t *testing.T) {
|
||||
Id: "something",
|
||||
},
|
||||
want: &action.GetTargetResponse{
|
||||
Target: &action.GetTarget{
|
||||
Details: &resource_object.Details{
|
||||
Id: "changed",
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
Target: &action.Target{
|
||||
Id: "changed",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "GetTarget, request, interrupt",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) (func(), error) {
|
||||
|
||||
fullMethod := "/zitadel.resources.action.v3alpha.ZITADELActions/GetTarget"
|
||||
dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) (func(), func() bool) {
|
||||
orgID := instance.DefaultOrg.Id
|
||||
projectID := ""
|
||||
userID := instance.Users.Get(integration.UserTypeIAMOwner).ID
|
||||
|
||||
// request received by target
|
||||
wantRequest := &middleware.ContextInfoRequest{FullMethod: fullMethod, InstanceID: instance.ID(), OrgID: orgID, ProjectID: projectID, UserID: userID, Request: request}
|
||||
urlRequest, closeRequest := testServerCall(wantRequest, 0, http.StatusInternalServerError, &action.GetTargetRequest{Id: "notchanged"})
|
||||
urlRequest, closeRequest, calledRequest, _ := integration.TestServerCall(wantRequest, 0, http.StatusInternalServerError, &action.GetTargetRequest{Id: "notchanged"})
|
||||
|
||||
targetRequest := waitForTarget(ctx, t, instance, urlRequest, domain.TargetTypeCall, true)
|
||||
waitForExecutionOnCondition(ctx, t, instance, conditionRequestFullMethod(fullMethod), executionTargetsSingleTarget(targetRequest.GetDetails().GetId()))
|
||||
waitForExecutionOnCondition(ctx, t, instance, conditionRequestFullMethod(fullMethod), executionTargetsSingleTarget(targetRequest.GetId()))
|
||||
// GetTarget with used target
|
||||
request.Id = targetRequest.GetDetails().GetId()
|
||||
request.Id = targetRequest.GetId()
|
||||
return func() {
|
||||
closeRequest()
|
||||
}, nil
|
||||
closeRequest()
|
||||
}, func() bool {
|
||||
return calledRequest() == 1
|
||||
}
|
||||
},
|
||||
clean: func(ctx context.Context) {
|
||||
instance.DeleteExecution(ctx, t, conditionRequestFullMethod(fullMethod))
|
||||
@@ -205,9 +203,7 @@ func TestServer_ExecutionTarget(t *testing.T) {
|
||||
{
|
||||
name: "GetTarget, response, interrupt",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) (func(), error) {
|
||||
|
||||
fullMethod := "/zitadel.resources.action.v3alpha.ZITADELActions/GetTarget"
|
||||
dep: func(ctx context.Context, request *action.GetTargetRequest, response *action.GetTargetResponse) (func(), func() bool) {
|
||||
orgID := instance.DefaultOrg.Id
|
||||
projectID := ""
|
||||
userID := instance.Users.Get(integration.UserTypeIAMOwner).ID
|
||||
@@ -219,29 +215,33 @@ func TestServer_ExecutionTarget(t *testing.T) {
|
||||
targetCreated := instance.CreateTarget(ctx, t, targetCreatedName, targetCreatedURL, domain.TargetTypeCall, false)
|
||||
|
||||
// GetTarget with used target
|
||||
request.Id = targetCreated.GetDetails().GetId()
|
||||
request.Id = targetCreated.GetId()
|
||||
|
||||
// expected response from the GetTarget
|
||||
expectedResponse := &action.GetTargetResponse{
|
||||
Target: &action.GetTarget{
|
||||
Details: targetCreated.GetDetails(),
|
||||
Config: &action.Target{
|
||||
Name: targetCreatedName,
|
||||
Endpoint: targetCreatedURL,
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
Target: &action.Target{
|
||||
Id: targetCreated.GetId(),
|
||||
CreationDate: targetCreated.GetCreationDate(),
|
||||
ChangeDate: targetCreated.GetCreationDate(),
|
||||
Name: targetCreatedName,
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.RESTCall{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
Timeout: durationpb.New(5 * time.Second),
|
||||
Endpoint: targetCreatedURL,
|
||||
SigningKey: targetCreated.GetSigningKey(),
|
||||
},
|
||||
}
|
||||
// content for partial update
|
||||
changedResponse := &action.GetTargetResponse{
|
||||
Target: &action.GetTarget{
|
||||
Details: &resource_object.Details{
|
||||
Id: "changed",
|
||||
Target: &action.Target{
|
||||
Id: "changed",
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.RESTCall{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -257,13 +257,15 @@ func TestServer_ExecutionTarget(t *testing.T) {
|
||||
Response: expectedResponse,
|
||||
}
|
||||
// after request with different targetID, return changed response
|
||||
targetResponseURL, closeResponse := testServerCall(wantResponse, 0, http.StatusInternalServerError, changedResponse)
|
||||
targetResponseURL, closeResponse, calledResponse, _ := integration.TestServerCall(wantResponse, 0, http.StatusInternalServerError, changedResponse)
|
||||
|
||||
targetResponse := waitForTarget(ctx, t, instance, targetResponseURL, domain.TargetTypeCall, true)
|
||||
waitForExecutionOnCondition(ctx, t, instance, conditionResponseFullMethod(fullMethod), executionTargetsSingleTarget(targetResponse.GetDetails().GetId()))
|
||||
waitForExecutionOnCondition(ctx, t, instance, conditionResponseFullMethod(fullMethod), executionTargetsSingleTarget(targetResponse.GetId()))
|
||||
return func() {
|
||||
closeResponse()
|
||||
}, nil
|
||||
closeResponse()
|
||||
}, func() bool {
|
||||
return calledResponse() == 1
|
||||
}
|
||||
},
|
||||
clean: func(ctx context.Context) {
|
||||
instance.DeleteExecution(ctx, t, conditionResponseFullMethod(fullMethod))
|
||||
@@ -274,29 +276,200 @@ func TestServer_ExecutionTarget(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.dep != nil {
|
||||
close, err := tt.dep(tt.ctx, tt.req, tt.want)
|
||||
require.NoError(t, err)
|
||||
defer close()
|
||||
}
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(isolatedIAMOwnerCTX, time.Minute)
|
||||
closeF, calledF := tt.dep(tt.ctx, tt.req, tt.want)
|
||||
defer closeF()
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tt.ctx, time.Minute)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
got, err := instance.Client.ActionV3Alpha.GetTarget(tt.ctx, tt.req)
|
||||
got, err := instance.Client.ActionV2beta.GetTarget(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(ttt, err)
|
||||
return
|
||||
}
|
||||
require.NoError(ttt, err)
|
||||
|
||||
integration.AssertResourceDetails(ttt, tt.want.GetTarget().GetDetails(), got.GetTarget().GetDetails())
|
||||
tt.want.Target.Details = got.GetTarget().GetDetails()
|
||||
assert.EqualExportedValues(ttt, tt.want.GetTarget().GetConfig(), got.GetTarget().GetConfig())
|
||||
assert.EqualExportedValues(ttt, tt.want.GetTarget(), got.GetTarget())
|
||||
|
||||
}, retryDuration, tick, "timeout waiting for expected execution result")
|
||||
|
||||
if tt.clean != nil {
|
||||
tt.clean(tt.ctx)
|
||||
}
|
||||
require.True(t, calledF())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ExecutionTarget_Event(t *testing.T) {
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
event := "session.added"
|
||||
urlRequest, closeF, calledF, resetF := integration.TestServerCall(nil, 0, http.StatusOK, nil)
|
||||
defer closeF()
|
||||
|
||||
targetResponse := waitForTarget(isolatedIAMOwnerCTX, t, instance, urlRequest, domain.TargetTypeWebhook, true)
|
||||
waitForExecutionOnCondition(isolatedIAMOwnerCTX, t, instance, conditionEvent(event), executionTargetsSingleTarget(targetResponse.GetId()))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
eventCount int
|
||||
expectedCalls int
|
||||
clean func(context.Context)
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "event, 1 session.added, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
eventCount: 1,
|
||||
expectedCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "event, 5 session.added, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
eventCount: 5,
|
||||
expectedCalls: 5,
|
||||
},
|
||||
{
|
||||
name: "event, 50 session.added, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
eventCount: 50,
|
||||
expectedCalls: 50,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// reset the count of the target
|
||||
resetF()
|
||||
|
||||
for i := 0; i < tt.eventCount; i++ {
|
||||
_, err := instance.Client.SessionV2.CreateSession(tt.ctx, &session.CreateSessionRequest{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// wait for called target
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tt.ctx, time.Minute)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
assert.True(ttt, calledF() == tt.expectedCalls)
|
||||
}, retryDuration, tick, "timeout waiting for expected execution result")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ExecutionTarget_Event_LongerThanTargetTimeout(t *testing.T) {
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
event := "session.added"
|
||||
// call takes longer than timeout of target
|
||||
urlRequest, closeF, calledF, resetF := integration.TestServerCall(nil, 5*time.Second, http.StatusOK, nil)
|
||||
defer closeF()
|
||||
|
||||
targetResponse := waitForTarget(isolatedIAMOwnerCTX, t, instance, urlRequest, domain.TargetTypeWebhook, true)
|
||||
waitForExecutionOnCondition(isolatedIAMOwnerCTX, t, instance, conditionEvent(event), executionTargetsSingleTarget(targetResponse.GetId()))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
eventCount int
|
||||
expectedCalls int
|
||||
clean func(context.Context)
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "event, 1 session.added, error logs",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
eventCount: 1,
|
||||
expectedCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "event, 5 session.added, error logs",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
eventCount: 5,
|
||||
expectedCalls: 5,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// reset the count of the target
|
||||
resetF()
|
||||
|
||||
for i := 0; i < tt.eventCount; i++ {
|
||||
_, err := instance.Client.SessionV2.CreateSession(tt.ctx, &session.CreateSessionRequest{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// wait for called target
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tt.ctx, time.Minute)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
assert.True(ttt, calledF() == tt.expectedCalls)
|
||||
}, retryDuration, tick, "timeout waiting for expected execution result")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ExecutionTarget_Event_LongerThanTransactionTimeout(t *testing.T) {
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
event := "session.added"
|
||||
urlRequest, closeF, calledF, resetF := integration.TestServerCall(nil, 1*time.Second, http.StatusOK, nil)
|
||||
defer closeF()
|
||||
|
||||
targetResponse := waitForTarget(isolatedIAMOwnerCTX, t, instance, urlRequest, domain.TargetTypeWebhook, true)
|
||||
waitForExecutionOnCondition(isolatedIAMOwnerCTX, t, instance, conditionEvent(event), executionTargetsSingleTarget(targetResponse.GetId()))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
eventCount int
|
||||
expectedCalls int
|
||||
clean func(context.Context)
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "event, 1 session.added, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
eventCount: 1,
|
||||
expectedCalls: 1,
|
||||
},
|
||||
{
|
||||
name: "event, 5 session.added, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
eventCount: 5,
|
||||
expectedCalls: 5,
|
||||
},
|
||||
{
|
||||
name: "event, 5 session.added, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
eventCount: 5,
|
||||
expectedCalls: 5,
|
||||
},
|
||||
{
|
||||
name: "event, 20 session.added, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
eventCount: 20,
|
||||
expectedCalls: 20,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// reset the count of the target
|
||||
resetF()
|
||||
|
||||
for i := 0; i < tt.eventCount; i++ {
|
||||
_, err := instance.Client.SessionV2.CreateSession(tt.ctx, &session.CreateSessionRequest{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// wait for called target
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tt.ctx, time.Minute)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
assert.True(ttt, calledF() == tt.expectedCalls)
|
||||
}, retryDuration, tick, "timeout waiting for expected execution result")
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -306,7 +479,7 @@ func waitForExecutionOnCondition(ctx context.Context, t *testing.T, instance *in
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(ctx, time.Minute)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
got, err := instance.Client.ActionV3Alpha.SearchExecutions(ctx, &action.SearchExecutionsRequest{
|
||||
got, err := instance.Client.ActionV2beta.ListExecutions(ctx, &action.ListExecutionsRequest{
|
||||
Filters: []*action.ExecutionSearchFilter{
|
||||
{Filter: &action.ExecutionSearchFilter_InConditionsFilter{
|
||||
InConditionsFilter: &action.InConditionsFilter{Conditions: []*action.Condition{condition}},
|
||||
@@ -319,7 +492,7 @@ func waitForExecutionOnCondition(ctx context.Context, t *testing.T, instance *in
|
||||
if !assert.Len(ttt, got.GetResult(), 1) {
|
||||
return
|
||||
}
|
||||
gotTargets := got.GetResult()[0].GetExecution().GetTargets()
|
||||
gotTargets := got.GetResult()[0].GetTargets()
|
||||
// always first check length, otherwise its failed anyway
|
||||
if assert.Len(ttt, gotTargets, len(targets)) {
|
||||
for i := range targets {
|
||||
@@ -335,10 +508,10 @@ func waitForTarget(ctx context.Context, t *testing.T, instance *integration.Inst
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(ctx, time.Minute)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
got, err := instance.Client.ActionV3Alpha.SearchTargets(ctx, &action.SearchTargetsRequest{
|
||||
got, err := instance.Client.ActionV2beta.ListTargets(ctx, &action.ListTargetsRequest{
|
||||
Filters: []*action.TargetSearchFilter{
|
||||
{Filter: &action.TargetSearchFilter_InTargetIdsFilter{
|
||||
InTargetIdsFilter: &action.InTargetIDsFilter{TargetIds: []string{resp.GetDetails().GetId()}},
|
||||
InTargetIdsFilter: &action.InTargetIDsFilter{TargetIds: []string{resp.GetId()}},
|
||||
}},
|
||||
},
|
||||
})
|
||||
@@ -348,7 +521,7 @@ func waitForTarget(ctx context.Context, t *testing.T, instance *integration.Inst
|
||||
if !assert.Len(ttt, got.GetResult(), 1) {
|
||||
return
|
||||
}
|
||||
config := got.GetResult()[0].GetConfig()
|
||||
config := got.GetResult()[0]
|
||||
assert.Equal(ttt, config.GetEndpoint(), endpoint)
|
||||
switch ty {
|
||||
case domain.TargetTypeWebhook:
|
||||
@@ -392,50 +565,16 @@ func conditionResponseFullMethod(fullMethod string) *action.Condition {
|
||||
}
|
||||
}
|
||||
|
||||
func testServerCall(
|
||||
reqBody interface{},
|
||||
sleep time.Duration,
|
||||
statusCode int,
|
||||
respBody interface{},
|
||||
) (string, func()) {
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
data, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
http.Error(w, "error, marshall: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
sentBody, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "error, read body: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(data, sentBody) {
|
||||
http.Error(w, "error, equal:\n"+string(data)+"\nsent:\n"+string(sentBody), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if statusCode != http.StatusOK {
|
||||
http.Error(w, "error, statusCode", statusCode)
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(sleep)
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
resp, err := json.Marshal(respBody)
|
||||
if err != nil {
|
||||
http.Error(w, "error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if _, err := io.WriteString(w, string(resp)); err != nil {
|
||||
http.Error(w, "error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
func conditionEvent(event string) *action.Condition {
|
||||
return &action.Condition{
|
||||
ConditionType: &action.Condition_Event{
|
||||
Event: &action.EventExecution{
|
||||
Condition: &action.EventExecution_Event{
|
||||
Event: event,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(handler))
|
||||
|
||||
return server.URL, server.Close
|
||||
}
|
||||
|
||||
func conditionFunction(function string) *action.Condition {
|
||||
@@ -643,10 +782,10 @@ func expectPreUserinfoExecution(ctx context.Context, t *testing.T, instance *int
|
||||
}
|
||||
expectedContextInfo := contextInfoForUserOIDC(instance, "function/preuserinfo", userResp, userEmail, userPhone)
|
||||
|
||||
targetURL, closeF := testServerCall(expectedContextInfo, 0, http.StatusOK, response)
|
||||
targetURL, closeF, _, _ := integration.TestServerCall(expectedContextInfo, 0, http.StatusOK, response)
|
||||
|
||||
targetResp := waitForTarget(ctx, t, instance, targetURL, domain.TargetTypeCall, true)
|
||||
waitForExecutionOnCondition(ctx, t, instance, conditionFunction("preuserinfo"), executionTargetsSingleTarget(targetResp.GetDetails().GetId()))
|
||||
waitForExecutionOnCondition(ctx, t, instance, conditionFunction("preuserinfo"), executionTargetsSingleTarget(targetResp.GetId()))
|
||||
return userResp.GetUserId(), closeF
|
||||
}
|
||||
|
||||
@@ -949,10 +1088,10 @@ func expectPreAccessTokenExecution(ctx context.Context, t *testing.T, instance *
|
||||
}
|
||||
expectedContextInfo := contextInfoForUserOIDC(instance, "function/preaccesstoken", userResp, userEmail, userPhone)
|
||||
|
||||
targetURL, closeF := testServerCall(expectedContextInfo, 0, http.StatusOK, response)
|
||||
targetURL, closeF, _, _ := integration.TestServerCall(expectedContextInfo, 0, http.StatusOK, response)
|
||||
|
||||
targetResp := waitForTarget(ctx, t, instance, targetURL, domain.TargetTypeCall, true)
|
||||
waitForExecutionOnCondition(ctx, t, instance, conditionFunction("preaccesstoken"), executionTargetsSingleTarget(targetResp.GetDetails().GetId()))
|
||||
waitForExecutionOnCondition(ctx, t, instance, conditionFunction("preaccesstoken"), executionTargetsSingleTarget(targetResp.GetId()))
|
||||
return userResp.GetUserId(), closeF
|
||||
}
|
||||
|
||||
@@ -1115,10 +1254,10 @@ func expectPreSAMLResponseExecution(ctx context.Context, t *testing.T, instance
|
||||
}
|
||||
expectedContextInfo := contextInfoForUserSAML(instance, "function/presamlresponse", userResp, userEmail, userPhone)
|
||||
|
||||
targetURL, closeF := testServerCall(expectedContextInfo, 0, http.StatusOK, response)
|
||||
targetURL, closeF, _, _ := integration.TestServerCall(expectedContextInfo, 0, http.StatusOK, response)
|
||||
|
||||
targetResp := waitForTarget(ctx, t, instance, targetURL, domain.TargetTypeCall, true)
|
||||
waitForExecutionOnCondition(ctx, t, instance, conditionFunction("presamlresponse"), executionTargetsSingleTarget(targetResp.GetDetails().GetId()))
|
||||
waitForExecutionOnCondition(ctx, t, instance, conditionFunction("presamlresponse"), executionTargetsSingleTarget(targetResp.GetId()))
|
||||
|
||||
return userResp.GetUserId(), closeF
|
||||
}
|
@@ -5,15 +5,14 @@ package action_test
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||
resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta"
|
||||
)
|
||||
|
||||
func executionTargetsSingleTarget(id string) []*action.ExecutionTargetType {
|
||||
@@ -31,11 +30,11 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
req *action.SetExecutionRequest
|
||||
want *action.SetExecutionResponse
|
||||
wantErr bool
|
||||
name string
|
||||
ctx context.Context
|
||||
req *action.SetExecutionRequest
|
||||
wantSetDate bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission",
|
||||
@@ -60,9 +59,7 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
Request: &action.RequestExecution{},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -79,9 +76,7 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -98,19 +93,9 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantSetDate: true,
|
||||
},
|
||||
{
|
||||
name: "service, not existing",
|
||||
@@ -125,9 +110,7 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -144,19 +127,9 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantSetDate: true,
|
||||
},
|
||||
{
|
||||
name: "all, ok",
|
||||
@@ -171,33 +144,24 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantSetDate: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// We want to have the same response no matter how often we call the function
|
||||
instance.Client.ActionV3Alpha.SetExecution(tt.ctx, tt.req)
|
||||
got, err := instance.Client.ActionV3Alpha.SetExecution(tt.ctx, tt.req)
|
||||
creationDate := time.Now().UTC()
|
||||
got, err := instance.Client.ActionV2beta.SetExecution(tt.ctx, tt.req)
|
||||
setDate := time.Now().UTC()
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
|
||||
integration.AssertResourceDetails(t, tt.want.Details, got.Details)
|
||||
assertSetExecutionResponse(t, creationDate, setDate, tt.wantSetDate, got)
|
||||
|
||||
// cleanup to not impact other requests
|
||||
instance.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||
@@ -205,6 +169,18 @@ func TestServer_SetExecution_Request(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func assertSetExecutionResponse(t *testing.T, creationDate, setDate time.Time, expectedSetDate bool, actualResp *action.SetExecutionResponse) {
|
||||
if expectedSetDate {
|
||||
if !setDate.IsZero() {
|
||||
assert.WithinRange(t, actualResp.GetSetDate().AsTime(), creationDate, setDate)
|
||||
} else {
|
||||
assert.WithinRange(t, actualResp.GetSetDate().AsTime(), creationDate, time.Now().UTC())
|
||||
}
|
||||
} else {
|
||||
assert.Nil(t, actualResp.SetDate)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
@@ -221,7 +197,7 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||
}
|
||||
instance.SetExecution(isolatedIAMOwnerCTX, t,
|
||||
executionCond,
|
||||
executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
executionTargetsSingleTarget(targetResp.GetId()),
|
||||
)
|
||||
|
||||
circularExecutionService := &action.Condition{
|
||||
@@ -252,20 +228,18 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
req *action.SetExecutionRequest
|
||||
want *action.SetExecutionResponse
|
||||
wantErr bool
|
||||
name string
|
||||
ctx context.Context
|
||||
req *action.SetExecutionRequest
|
||||
wantSetDate bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "method, circular error",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: circularExecutionService,
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleInclude(circularExecutionMethod),
|
||||
},
|
||||
Targets: executionTargetsSingleInclude(circularExecutionMethod),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -282,19 +256,9 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleInclude(executionCond),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
Targets: executionTargetsSingleInclude(executionCond),
|
||||
},
|
||||
wantSetDate: true,
|
||||
},
|
||||
{
|
||||
name: "service, ok",
|
||||
@@ -304,38 +268,28 @@ func TestServer_SetExecution_Request_Include(t *testing.T) {
|
||||
ConditionType: &action.Condition_Request{
|
||||
Request: &action.RequestExecution{
|
||||
Condition: &action.RequestExecution_Service{
|
||||
Service: "zitadel.session.v2beta.SessionService",
|
||||
Service: "zitadel.user.v2beta.UserService",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleInclude(executionCond),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
Targets: executionTargetsSingleInclude(executionCond),
|
||||
},
|
||||
wantSetDate: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// We want to have the same response no matter how often we call the function
|
||||
instance.Client.ActionV3Alpha.SetExecution(tt.ctx, tt.req)
|
||||
got, err := instance.Client.ActionV3Alpha.SetExecution(tt.ctx, tt.req)
|
||||
creationDate := time.Now().UTC()
|
||||
got, err := instance.Client.ActionV2beta.SetExecution(tt.ctx, tt.req)
|
||||
setDate := time.Now().UTC()
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertResourceDetails(t, tt.want.Details, got.Details)
|
||||
assertSetExecutionResponse(t, creationDate, setDate, tt.wantSetDate, got)
|
||||
|
||||
// cleanup to not impact other requests
|
||||
instance.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||
@@ -350,11 +304,11 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
||||
targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
req *action.SetExecutionRequest
|
||||
want *action.SetExecutionResponse
|
||||
wantErr bool
|
||||
name string
|
||||
ctx context.Context
|
||||
req *action.SetExecutionRequest
|
||||
wantSetDate bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission",
|
||||
@@ -379,9 +333,7 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
||||
Response: &action.ResponseExecution{},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -398,9 +350,7 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -417,19 +367,9 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantSetDate: true,
|
||||
},
|
||||
{
|
||||
name: "service, not existing",
|
||||
@@ -444,9 +384,7 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -463,19 +401,9 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantSetDate: true,
|
||||
},
|
||||
{
|
||||
name: "all, ok",
|
||||
@@ -490,33 +418,23 @@ func TestServer_SetExecution_Response(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantSetDate: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// We want to have the same response no matter how often we call the function
|
||||
instance.Client.ActionV3Alpha.SetExecution(tt.ctx, tt.req)
|
||||
got, err := instance.Client.ActionV3Alpha.SetExecution(tt.ctx, tt.req)
|
||||
creationDate := time.Now().UTC()
|
||||
got, err := instance.Client.ActionV2beta.SetExecution(tt.ctx, tt.req)
|
||||
setDate := time.Now().UTC()
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertResourceDetails(t, tt.want.Details, got.Details)
|
||||
assertSetExecutionResponse(t, creationDate, setDate, tt.wantSetDate, got)
|
||||
|
||||
// cleanup to not impact other requests
|
||||
instance.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||
@@ -531,11 +449,11 @@ func TestServer_SetExecution_Event(t *testing.T) {
|
||||
targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
req *action.SetExecutionRequest
|
||||
want *action.SetExecutionResponse
|
||||
wantErr bool
|
||||
name string
|
||||
ctx context.Context
|
||||
req *action.SetExecutionRequest
|
||||
wantSetDate bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission",
|
||||
@@ -562,33 +480,27 @@ func TestServer_SetExecution_Event(t *testing.T) {
|
||||
Event: &action.EventExecution{},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
/*
|
||||
//TODO event existing check
|
||||
|
||||
{
|
||||
name: "event, not existing",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Event{
|
||||
Event: &action.EventExecution{
|
||||
Condition: &action.EventExecution_Event{
|
||||
Event: "xxx",
|
||||
},
|
||||
{
|
||||
name: "event, not existing",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Event{
|
||||
Event: &action.EventExecution{
|
||||
Condition: &action.EventExecution_Event{
|
||||
Event: "user.human.notexisting",
|
||||
},
|
||||
},
|
||||
},
|
||||
Targets: []string{targetResp.GetId()},
|
||||
},
|
||||
wantErr: true,
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
*/
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "event, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
@@ -597,72 +509,65 @@ func TestServer_SetExecution_Event(t *testing.T) {
|
||||
ConditionType: &action.Condition_Event{
|
||||
Event: &action.EventExecution{
|
||||
Condition: &action.EventExecution_Event{
|
||||
Event: "xxx",
|
||||
Event: "user.human.added",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantSetDate: true,
|
||||
},
|
||||
/*
|
||||
// TODO:
|
||||
|
||||
{
|
||||
name: "group, not existing",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Event{
|
||||
Event: &action.EventExecution{
|
||||
Condition: &action.EventExecution_Group{
|
||||
Group: "xxx",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Targets: []string{targetResp.GetId()},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
*/
|
||||
{
|
||||
name: "group, ok",
|
||||
name: "group, not existing",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Event{
|
||||
Event: &action.EventExecution{
|
||||
Condition: &action.EventExecution_Group{
|
||||
Group: "xxx",
|
||||
Group: "user.notexisting",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "group, level 1, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Event{
|
||||
Event: &action.EventExecution{
|
||||
Condition: &action.EventExecution_Group{
|
||||
Group: "user",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantSetDate: true,
|
||||
},
|
||||
{
|
||||
name: "group, level 2, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.SetExecutionRequest{
|
||||
Condition: &action.Condition{
|
||||
ConditionType: &action.Condition_Event{
|
||||
Event: &action.EventExecution{
|
||||
Condition: &action.EventExecution_Group{
|
||||
Group: "user.human",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantSetDate: true,
|
||||
},
|
||||
{
|
||||
name: "all, ok",
|
||||
@@ -677,33 +582,23 @@ func TestServer_SetExecution_Event(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantSetDate: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// We want to have the same response no matter how often we call the function
|
||||
instance.Client.ActionV3Alpha.SetExecution(tt.ctx, tt.req)
|
||||
got, err := instance.Client.ActionV3Alpha.SetExecution(tt.ctx, tt.req)
|
||||
creationDate := time.Now().UTC()
|
||||
got, err := instance.Client.ActionV2beta.SetExecution(tt.ctx, tt.req)
|
||||
setDate := time.Now().UTC()
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertResourceDetails(t, tt.want.Details, got.Details)
|
||||
assertSetExecutionResponse(t, creationDate, setDate, tt.wantSetDate, got)
|
||||
|
||||
// cleanup to not impact other requests
|
||||
instance.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
||||
@@ -718,11 +613,11 @@ func TestServer_SetExecution_Function(t *testing.T) {
|
||||
targetResp := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://notexisting", domain.TargetTypeWebhook, false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
req *action.SetExecutionRequest
|
||||
want *action.SetExecutionResponse
|
||||
wantErr bool
|
||||
name string
|
||||
ctx context.Context
|
||||
req *action.SetExecutionRequest
|
||||
wantSetDate bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission",
|
||||
@@ -747,9 +642,7 @@ func TestServer_SetExecution_Function(t *testing.T) {
|
||||
Response: &action.ResponseExecution{},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -762,9 +655,7 @@ func TestServer_SetExecution_Function(t *testing.T) {
|
||||
Function: &action.FunctionExecution{Name: "xxx"},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
@@ -777,33 +668,23 @@ func TestServer_SetExecution_Function(t *testing.T) {
|
||||
Function: &action.FunctionExecution{Name: "presamlresponse"},
|
||||
},
|
||||
},
|
||||
Execution: &action.Execution{
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetDetails().GetId()),
|
||||
},
|
||||
},
|
||||
want: &action.SetExecutionResponse{
|
||||
Details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
Targets: executionTargetsSingleTarget(targetResp.GetId()),
|
||||
},
|
||||
wantSetDate: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// We want to have the same response no matter how often we call the function
|
||||
instance.Client.ActionV3Alpha.SetExecution(tt.ctx, tt.req)
|
||||
got, err := instance.Client.ActionV3Alpha.SetExecution(tt.ctx, tt.req)
|
||||
creationDate := time.Now().UTC()
|
||||
got, err := instance.Client.ActionV2beta.SetExecution(tt.ctx, tt.req)
|
||||
setDate := time.Now().UTC()
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
integration.AssertResourceDetails(t, tt.want.Details, got.Details)
|
||||
assertSetExecutionResponse(t, creationDate, setDate, tt.wantSetDate, got)
|
||||
|
||||
// cleanup to not impact other requests
|
||||
instance.DeleteExecution(tt.ctx, t, tt.req.GetCondition())
|
File diff suppressed because it is too large
Load Diff
@@ -13,8 +13,8 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -60,7 +60,7 @@ func ensureFeatureEnabled(t *testing.T, instance *integration.Instance) {
|
||||
retryDuration, tick = integration.WaitForAndTickWithMaxDuration(ctx, 5*time.Minute)
|
||||
require.EventuallyWithT(t,
|
||||
func(ttt *assert.CollectT) {
|
||||
_, err := instance.Client.ActionV3Alpha.ListExecutionMethods(ctx, &action.ListExecutionMethodsRequest{})
|
||||
_, err := instance.Client.ActionV2beta.ListExecutionMethods(ctx, &action.ListExecutionMethodsRequest{})
|
||||
assert.NoError(ttt, err)
|
||||
},
|
||||
retryDuration,
|
553
internal/api/grpc/action/v2beta/integration_test/target_test.go
Normal file
553
internal/api/grpc/action/v2beta/integration_test/target_test.go
Normal file
@@ -0,0 +1,553 @@
|
||||
//go:build integration
|
||||
|
||||
package action_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"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/integration"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta"
|
||||
)
|
||||
|
||||
func TestServer_CreateTarget(t *testing.T) {
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
type want struct {
|
||||
id bool
|
||||
creationDate bool
|
||||
signingKey bool
|
||||
}
|
||||
alreadyExistingTargetName := gofakeit.AppName()
|
||||
instance.CreateTarget(isolatedIAMOwnerCTX, t, alreadyExistingTargetName, "https://example.com", domain.TargetTypeAsync, false)
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
req *action.CreateTargetRequest
|
||||
want
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission",
|
||||
ctx: instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||
req: &action.CreateTargetRequest{
|
||||
Name: gofakeit.Name(),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty name",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.CreateTargetRequest{
|
||||
Name: "",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty type",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.CreateTargetRequest{
|
||||
Name: gofakeit.Name(),
|
||||
TargetType: nil,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty webhook url",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.CreateTargetRequest{
|
||||
Name: gofakeit.Name(),
|
||||
TargetType: &action.CreateTargetRequest_RestWebhook{
|
||||
RestWebhook: &action.RESTWebhook{},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty request response url",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.CreateTargetRequest{
|
||||
Name: gofakeit.Name(),
|
||||
TargetType: &action.CreateTargetRequest_RestCall{
|
||||
RestCall: &action.RESTCall{},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty timeout",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.CreateTargetRequest{
|
||||
Name: gofakeit.Name(),
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.CreateTargetRequest_RestWebhook{
|
||||
RestWebhook: &action.RESTWebhook{},
|
||||
},
|
||||
Timeout: nil,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "async, already existing, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.CreateTargetRequest{
|
||||
Name: alreadyExistingTargetName,
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.CreateTargetRequest_RestAsync{
|
||||
RestAsync: &action.RESTAsync{},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "async, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.CreateTargetRequest{
|
||||
Name: gofakeit.Name(),
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.CreateTargetRequest_RestAsync{
|
||||
RestAsync: &action.RESTAsync{},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
want: want{
|
||||
id: true,
|
||||
creationDate: true,
|
||||
signingKey: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "webhook, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.CreateTargetRequest{
|
||||
Name: gofakeit.Name(),
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.CreateTargetRequest_RestWebhook{
|
||||
RestWebhook: &action.RESTWebhook{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
want: want{
|
||||
id: true,
|
||||
creationDate: true,
|
||||
signingKey: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "webhook, interrupt on error, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.CreateTargetRequest{
|
||||
Name: gofakeit.Name(),
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.CreateTargetRequest_RestWebhook{
|
||||
RestWebhook: &action.RESTWebhook{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
want: want{
|
||||
id: true,
|
||||
creationDate: true,
|
||||
signingKey: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "call, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.CreateTargetRequest{
|
||||
Name: gofakeit.Name(),
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.CreateTargetRequest_RestCall{
|
||||
RestCall: &action.RESTCall{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
want: want{
|
||||
id: true,
|
||||
creationDate: true,
|
||||
signingKey: true,
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "call, interruptOnError, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.CreateTargetRequest{
|
||||
Name: gofakeit.Name(),
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.CreateTargetRequest_RestCall{
|
||||
RestCall: &action.RESTCall{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
want: want{
|
||||
id: true,
|
||||
creationDate: true,
|
||||
signingKey: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
creationDate := time.Now().UTC()
|
||||
got, err := instance.Client.ActionV2beta.CreateTarget(tt.ctx, tt.req)
|
||||
changeDate := time.Now().UTC()
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assertCreateTargetResponse(t, creationDate, changeDate, tt.want.creationDate, tt.want.id, tt.want.signingKey, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func assertCreateTargetResponse(t *testing.T, creationDate, changeDate time.Time, expectedCreationDate, expectedID, expectedSigningKey bool, actualResp *action.CreateTargetResponse) {
|
||||
if expectedCreationDate {
|
||||
if !changeDate.IsZero() {
|
||||
assert.WithinRange(t, actualResp.GetCreationDate().AsTime(), creationDate, changeDate)
|
||||
} else {
|
||||
assert.WithinRange(t, actualResp.GetCreationDate().AsTime(), creationDate, time.Now().UTC())
|
||||
}
|
||||
} else {
|
||||
assert.Nil(t, actualResp.CreationDate)
|
||||
}
|
||||
|
||||
if expectedID {
|
||||
assert.NotEmpty(t, actualResp.GetId())
|
||||
} else {
|
||||
assert.Nil(t, actualResp.Id)
|
||||
}
|
||||
|
||||
if expectedSigningKey {
|
||||
assert.NotEmpty(t, actualResp.GetSigningKey())
|
||||
} else {
|
||||
assert.Nil(t, actualResp.SigningKey)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_UpdateTarget(t *testing.T) {
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *action.UpdateTargetRequest
|
||||
}
|
||||
type want struct {
|
||||
change bool
|
||||
changeDate bool
|
||||
signingKey bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
prepare func(request *action.UpdateTargetRequest)
|
||||
args args
|
||||
want want
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission",
|
||||
prepare: func(request *action.UpdateTargetRequest) {
|
||||
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||
request.Id = targetID
|
||||
},
|
||||
args: args{
|
||||
ctx: instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||
req: &action.UpdateTargetRequest{
|
||||
Name: gu.Ptr(gofakeit.Name()),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "not existing",
|
||||
prepare: func(request *action.UpdateTargetRequest) {
|
||||
request.Id = "notexisting"
|
||||
return
|
||||
},
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.UpdateTargetRequest{
|
||||
Name: gu.Ptr(gofakeit.Name()),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "no change, ok",
|
||||
prepare: func(request *action.UpdateTargetRequest) {
|
||||
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||
request.Id = targetID
|
||||
},
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.UpdateTargetRequest{
|
||||
Endpoint: gu.Ptr("https://example.com"),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
change: false,
|
||||
changeDate: true,
|
||||
signingKey: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change name, ok",
|
||||
prepare: func(request *action.UpdateTargetRequest) {
|
||||
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||
request.Id = targetID
|
||||
},
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.UpdateTargetRequest{
|
||||
Name: gu.Ptr(gofakeit.Name()),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
change: true,
|
||||
changeDate: true,
|
||||
signingKey: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "regenerate signingkey, ok",
|
||||
prepare: func(request *action.UpdateTargetRequest) {
|
||||
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||
request.Id = targetID
|
||||
},
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.UpdateTargetRequest{
|
||||
ExpirationSigningKey: durationpb.New(0 * time.Second),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
change: true,
|
||||
changeDate: true,
|
||||
signingKey: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change type, ok",
|
||||
prepare: func(request *action.UpdateTargetRequest) {
|
||||
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||
request.Id = targetID
|
||||
},
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.UpdateTargetRequest{
|
||||
TargetType: &action.UpdateTargetRequest_RestCall{
|
||||
RestCall: &action.RESTCall{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
change: true,
|
||||
changeDate: true,
|
||||
signingKey: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change url, ok",
|
||||
prepare: func(request *action.UpdateTargetRequest) {
|
||||
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||
request.Id = targetID
|
||||
},
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.UpdateTargetRequest{
|
||||
Endpoint: gu.Ptr("https://example.com/hooks/new"),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
change: true,
|
||||
changeDate: true,
|
||||
signingKey: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change timeout, ok",
|
||||
prepare: func(request *action.UpdateTargetRequest) {
|
||||
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||
request.Id = targetID
|
||||
},
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.UpdateTargetRequest{
|
||||
Timeout: durationpb.New(20 * time.Second),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
change: true,
|
||||
changeDate: true,
|
||||
signingKey: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change type async, ok",
|
||||
prepare: func(request *action.UpdateTargetRequest) {
|
||||
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeAsync, false).GetId()
|
||||
request.Id = targetID
|
||||
},
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.UpdateTargetRequest{
|
||||
TargetType: &action.UpdateTargetRequest_RestAsync{
|
||||
RestAsync: &action.RESTAsync{},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
change: true,
|
||||
changeDate: true,
|
||||
signingKey: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
creationDate := time.Now().UTC()
|
||||
tt.prepare(tt.args.req)
|
||||
|
||||
got, err := instance.Client.ActionV2beta.UpdateTarget(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
changeDate := time.Time{}
|
||||
if tt.want.change {
|
||||
changeDate = time.Now().UTC()
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assertUpdateTargetResponse(t, creationDate, changeDate, tt.want.changeDate, tt.want.signingKey, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func assertUpdateTargetResponse(t *testing.T, creationDate, changeDate time.Time, expectedChangeDate, expectedSigningKey bool, actualResp *action.UpdateTargetResponse) {
|
||||
if expectedChangeDate {
|
||||
if !changeDate.IsZero() {
|
||||
assert.WithinRange(t, actualResp.GetChangeDate().AsTime(), creationDate, changeDate)
|
||||
} else {
|
||||
assert.WithinRange(t, actualResp.GetChangeDate().AsTime(), creationDate, time.Now().UTC())
|
||||
}
|
||||
} else {
|
||||
assert.Nil(t, actualResp.ChangeDate)
|
||||
}
|
||||
|
||||
if expectedSigningKey {
|
||||
assert.NotEmpty(t, actualResp.GetSigningKey())
|
||||
} else {
|
||||
assert.Nil(t, actualResp.SigningKey)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_DeleteTarget(t *testing.T) {
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
iamOwnerCtx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
prepare func(request *action.DeleteTargetRequest) (time.Time, time.Time)
|
||||
req *action.DeleteTargetRequest
|
||||
wantDeletionDate bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission",
|
||||
ctx: instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||
req: &action.DeleteTargetRequest{
|
||||
Id: "notexisting",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty id",
|
||||
ctx: iamOwnerCtx,
|
||||
req: &action.DeleteTargetRequest{
|
||||
Id: "",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "delete target, not existing",
|
||||
ctx: iamOwnerCtx,
|
||||
req: &action.DeleteTargetRequest{
|
||||
Id: "notexisting",
|
||||
},
|
||||
wantDeletionDate: false,
|
||||
},
|
||||
{
|
||||
name: "delete target",
|
||||
ctx: iamOwnerCtx,
|
||||
prepare: func(request *action.DeleteTargetRequest) (time.Time, time.Time) {
|
||||
creationDate := time.Now().UTC()
|
||||
targetID := instance.CreateTarget(iamOwnerCtx, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||
request.Id = targetID
|
||||
return creationDate, time.Time{}
|
||||
},
|
||||
req: &action.DeleteTargetRequest{},
|
||||
wantDeletionDate: true,
|
||||
},
|
||||
{
|
||||
name: "delete target, already removed",
|
||||
ctx: iamOwnerCtx,
|
||||
prepare: func(request *action.DeleteTargetRequest) (time.Time, time.Time) {
|
||||
creationDate := time.Now().UTC()
|
||||
targetID := instance.CreateTarget(iamOwnerCtx, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetId()
|
||||
request.Id = targetID
|
||||
instance.DeleteTarget(iamOwnerCtx, t, targetID)
|
||||
return creationDate, time.Now().UTC()
|
||||
},
|
||||
req: &action.DeleteTargetRequest{},
|
||||
wantDeletionDate: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var creationDate, deletionDate time.Time
|
||||
if tt.prepare != nil {
|
||||
creationDate, deletionDate = tt.prepare(tt.req)
|
||||
}
|
||||
got, err := instance.Client.ActionV2beta.DeleteTarget(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assertDeleteTargetResponse(t, creationDate, deletionDate, tt.wantDeletionDate, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func assertDeleteTargetResponse(t *testing.T, creationDate, deletionDate time.Time, expectedDeletionDate bool, actualResp *action.DeleteTargetResponse) {
|
||||
if expectedDeletionDate {
|
||||
if !deletionDate.IsZero() {
|
||||
assert.WithinRange(t, actualResp.GetDeletionDate().AsTime(), creationDate, deletionDate)
|
||||
} else {
|
||||
assert.WithinRange(t, actualResp.GetDeletionDate().AsTime(), creationDate, time.Now().UTC())
|
||||
}
|
||||
} else {
|
||||
assert.Nil(t, actualResp.DeletionDate)
|
||||
}
|
||||
}
|
@@ -5,14 +5,14 @@ import (
|
||||
"strings"
|
||||
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
resource_object "github.com/zitadel/zitadel/internal/api/grpc/resources/object/v3alpha"
|
||||
filter "github.com/zitadel/zitadel/internal/api/grpc/filter/v2beta"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -45,11 +45,11 @@ type Context interface {
|
||||
GetOwner() InstanceContext
|
||||
}
|
||||
|
||||
func (s *Server) SearchTargets(ctx context.Context, req *action.SearchTargetsRequest) (*action.SearchTargetsResponse, error) {
|
||||
func (s *Server) ListTargets(ctx context.Context, req *action.ListTargetsRequest) (*action.ListTargetsResponse, error) {
|
||||
if err := checkActionsEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries, err := s.searchTargetsRequestToModel(req)
|
||||
queries, err := s.ListTargetsRequestToModel(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -57,17 +57,17 @@ func (s *Server) SearchTargets(ctx context.Context, req *action.SearchTargetsReq
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &action.SearchTargetsResponse{
|
||||
Result: targetsToPb(resp.Targets),
|
||||
Details: resource_object.ToSearchDetailsPb(queries.SearchRequest, resp.SearchResponse),
|
||||
return &action.ListTargetsResponse{
|
||||
Result: targetsToPb(resp.Targets),
|
||||
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, resp.SearchResponse),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) SearchExecutions(ctx context.Context, req *action.SearchExecutionsRequest) (*action.SearchExecutionsResponse, error) {
|
||||
func (s *Server) ListExecutions(ctx context.Context, req *action.ListExecutionsRequest) (*action.ListExecutionsResponse, error) {
|
||||
if err := checkActionsEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
queries, err := s.searchExecutionsRequestToModel(req)
|
||||
queries, err := s.ListExecutionsRequestToModel(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -75,45 +75,50 @@ func (s *Server) SearchExecutions(ctx context.Context, req *action.SearchExecuti
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &action.SearchExecutionsResponse{
|
||||
Result: executionsToPb(resp.Executions),
|
||||
Details: resource_object.ToSearchDetailsPb(queries.SearchRequest, resp.SearchResponse),
|
||||
return &action.ListExecutionsResponse{
|
||||
Result: executionsToPb(resp.Executions),
|
||||
Pagination: filter.QueryToPaginationPb(queries.SearchRequest, resp.SearchResponse),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func targetsToPb(targets []*query.Target) []*action.GetTarget {
|
||||
t := make([]*action.GetTarget, len(targets))
|
||||
func targetsToPb(targets []*query.Target) []*action.Target {
|
||||
t := make([]*action.Target, len(targets))
|
||||
for i, target := range targets {
|
||||
t[i] = targetToPb(target)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func targetToPb(t *query.Target) *action.GetTarget {
|
||||
target := &action.GetTarget{
|
||||
Details: resource_object.DomainToDetailsPb(&t.ObjectDetails, object.OwnerType_OWNER_TYPE_INSTANCE, t.ResourceOwner),
|
||||
Config: &action.Target{
|
||||
Name: t.Name,
|
||||
Timeout: durationpb.New(t.Timeout),
|
||||
Endpoint: t.Endpoint,
|
||||
},
|
||||
func targetToPb(t *query.Target) *action.Target {
|
||||
target := &action.Target{
|
||||
Id: t.ObjectDetails.ID,
|
||||
Name: t.Name,
|
||||
Timeout: durationpb.New(t.Timeout),
|
||||
Endpoint: t.Endpoint,
|
||||
SigningKey: t.SigningKey,
|
||||
}
|
||||
switch t.TargetType {
|
||||
case domain.TargetTypeWebhook:
|
||||
target.Config.TargetType = &action.Target_RestWebhook{RestWebhook: &action.SetRESTWebhook{InterruptOnError: t.InterruptOnError}}
|
||||
target.TargetType = &action.Target_RestWebhook{RestWebhook: &action.RESTWebhook{InterruptOnError: t.InterruptOnError}}
|
||||
case domain.TargetTypeCall:
|
||||
target.Config.TargetType = &action.Target_RestCall{RestCall: &action.SetRESTCall{InterruptOnError: t.InterruptOnError}}
|
||||
target.TargetType = &action.Target_RestCall{RestCall: &action.RESTCall{InterruptOnError: t.InterruptOnError}}
|
||||
case domain.TargetTypeAsync:
|
||||
target.Config.TargetType = &action.Target_RestAsync{RestAsync: &action.SetRESTAsync{}}
|
||||
target.TargetType = &action.Target_RestAsync{RestAsync: &action.RESTAsync{}}
|
||||
default:
|
||||
target.Config.TargetType = nil
|
||||
target.TargetType = nil
|
||||
}
|
||||
|
||||
if !t.ObjectDetails.EventDate.IsZero() {
|
||||
target.ChangeDate = timestamppb.New(t.ObjectDetails.EventDate)
|
||||
}
|
||||
if !t.ObjectDetails.CreationDate.IsZero() {
|
||||
target.CreationDate = timestamppb.New(t.ObjectDetails.CreationDate)
|
||||
}
|
||||
return target
|
||||
}
|
||||
|
||||
func (s *Server) searchTargetsRequestToModel(req *action.SearchTargetsRequest) (*query.TargetSearchQueries, error) {
|
||||
offset, limit, asc, err := resource_object.SearchQueryPbToQuery(s.systemDefaults, req.Query)
|
||||
func (s *Server) ListTargetsRequestToModel(req *action.ListTargetsRequest) (*query.TargetSearchQueries, error) {
|
||||
offset, limit, asc, err := filter.PaginationPbToQuery(s.systemDefaults, req.Pagination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -155,7 +160,7 @@ func targetQueryToQuery(filter *action.TargetSearchFilter) (query.SearchQuery, e
|
||||
}
|
||||
|
||||
func targetNameQueryToQuery(q *action.TargetNameFilter) (query.SearchQuery, error) {
|
||||
return query.NewTargetNameSearchQuery(resource_object.TextMethodPbToQuery(q.Method), q.GetTargetName())
|
||||
return query.NewTargetNameSearchQuery(filter.TextMethodPbToQuery(q.Method), q.GetTargetName())
|
||||
}
|
||||
|
||||
func targetInTargetIdsQueryToQuery(q *action.InTargetIDsFilter) (query.SearchQuery, error) {
|
||||
@@ -210,8 +215,8 @@ func executionFieldNameToSortingColumn(field *action.ExecutionFieldName) query.C
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) searchExecutionsRequestToModel(req *action.SearchExecutionsRequest) (*query.ExecutionSearchQueries, error) {
|
||||
offset, limit, asc, err := resource_object.SearchQueryPbToQuery(s.systemDefaults, req.Query)
|
||||
func (s *Server) ListExecutionsRequestToModel(req *action.ListExecutionsRequest) (*query.ExecutionSearchQueries, error) {
|
||||
offset, limit, asc, err := filter.PaginationPbToQuery(s.systemDefaults, req.Pagination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -319,15 +324,15 @@ func conditionToID(q *action.Condition) (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func executionsToPb(executions []*query.Execution) []*action.GetExecution {
|
||||
e := make([]*action.GetExecution, len(executions))
|
||||
func executionsToPb(executions []*query.Execution) []*action.Execution {
|
||||
e := make([]*action.Execution, len(executions))
|
||||
for i, execution := range executions {
|
||||
e[i] = executionToPb(execution)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func executionToPb(e *query.Execution) *action.GetExecution {
|
||||
func executionToPb(e *query.Execution) *action.Execution {
|
||||
targets := make([]*action.ExecutionTargetType, len(e.Targets))
|
||||
for i := range e.Targets {
|
||||
switch e.Targets[i].Type {
|
||||
@@ -342,12 +347,17 @@ func executionToPb(e *query.Execution) *action.GetExecution {
|
||||
}
|
||||
}
|
||||
|
||||
return &action.GetExecution{
|
||||
Details: resource_object.DomainToDetailsPb(&e.ObjectDetails, object.OwnerType_OWNER_TYPE_INSTANCE, e.ResourceOwner),
|
||||
Execution: &action.Execution{
|
||||
Targets: targets,
|
||||
},
|
||||
exec := &action.Execution{
|
||||
Condition: executionIDToCondition(e.ID),
|
||||
Targets: targets,
|
||||
}
|
||||
if !e.ObjectDetails.EventDate.IsZero() {
|
||||
exec.ChangeDate = timestamppb.New(e.ObjectDetails.EventDate)
|
||||
}
|
||||
if !e.ObjectDetails.CreationDate.IsZero() {
|
||||
exec.CreationDate = timestamppb.New(e.ObjectDetails.CreationDate)
|
||||
}
|
||||
return exec
|
||||
}
|
||||
|
||||
func executionIDToCondition(include string) *action.Condition {
|
@@ -11,13 +11,13 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta"
|
||||
)
|
||||
|
||||
var _ action.ZITADELActionsServer = (*Server)(nil)
|
||||
var _ action.ActionServiceServer = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
action.UnimplementedZITADELActionsServer
|
||||
action.UnimplementedActionServiceServer
|
||||
systemDefaults systemdefaults.SystemDefaults
|
||||
command *command.Commands
|
||||
query *query.Queries
|
||||
@@ -47,23 +47,23 @@ func CreateServer(
|
||||
}
|
||||
|
||||
func (s *Server) RegisterServer(grpcServer *grpc.Server) {
|
||||
action.RegisterZITADELActionsServer(grpcServer, s)
|
||||
action.RegisterActionServiceServer(grpcServer, s)
|
||||
}
|
||||
|
||||
func (s *Server) AppName() string {
|
||||
return action.ZITADELActions_ServiceDesc.ServiceName
|
||||
return action.ActionService_ServiceDesc.ServiceName
|
||||
}
|
||||
|
||||
func (s *Server) MethodPrefix() string {
|
||||
return action.ZITADELActions_ServiceDesc.ServiceName
|
||||
return action.ActionService_ServiceDesc.ServiceName
|
||||
}
|
||||
|
||||
func (s *Server) AuthMethods() authz.MethodMapping {
|
||||
return action.ZITADELActions_AuthMethods
|
||||
return action.ActionService_AuthMethods
|
||||
}
|
||||
|
||||
func (s *Server) RegisterGateway() server.RegisterGatewayFunc {
|
||||
return action.RegisterZITADELActionsHandler
|
||||
return action.RegisterActionServiceHandler
|
||||
}
|
||||
|
||||
func checkActionsEnabled(ctx context.Context) error {
|
@@ -4,14 +4,13 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
resource_object "github.com/zitadel/zitadel/internal/api/grpc/resources/object/v3alpha"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta"
|
||||
)
|
||||
|
||||
func (s *Server) CreateTarget(ctx context.Context, req *action.CreateTargetRequest) (*action.CreateTargetResponse, error) {
|
||||
@@ -20,29 +19,38 @@ func (s *Server) CreateTarget(ctx context.Context, req *action.CreateTargetReque
|
||||
}
|
||||
add := createTargetToCommand(req)
|
||||
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||
details, err := s.command.AddTarget(ctx, add, instanceID)
|
||||
createdAt, err := s.command.AddTarget(ctx, add, instanceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var creationDate *timestamppb.Timestamp
|
||||
if !createdAt.IsZero() {
|
||||
creationDate = timestamppb.New(createdAt)
|
||||
}
|
||||
return &action.CreateTargetResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID),
|
||||
SigningKey: add.SigningKey,
|
||||
Id: add.AggregateID,
|
||||
CreationDate: creationDate,
|
||||
SigningKey: add.SigningKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) PatchTarget(ctx context.Context, req *action.PatchTargetRequest) (*action.PatchTargetResponse, error) {
|
||||
func (s *Server) UpdateTarget(ctx context.Context, req *action.UpdateTargetRequest) (*action.UpdateTargetResponse, error) {
|
||||
if err := checkActionsEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||
patch := patchTargetToCommand(req)
|
||||
details, err := s.command.ChangeTarget(ctx, patch, instanceID)
|
||||
update := updateTargetToCommand(req)
|
||||
changedAt, err := s.command.ChangeTarget(ctx, update, instanceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &action.PatchTargetResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID),
|
||||
SigningKey: patch.SigningKey,
|
||||
var changeDate *timestamppb.Timestamp
|
||||
if !changedAt.IsZero() {
|
||||
changeDate = timestamppb.New(changedAt)
|
||||
}
|
||||
return &action.UpdateTargetResponse{
|
||||
ChangeDate: changeDate,
|
||||
SigningKey: update.SigningKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -51,74 +59,76 @@ func (s *Server) DeleteTarget(ctx context.Context, req *action.DeleteTargetReque
|
||||
return nil, err
|
||||
}
|
||||
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||
details, err := s.command.DeleteTarget(ctx, req.GetId(), instanceID)
|
||||
deletedAt, err := s.command.DeleteTarget(ctx, req.GetId(), instanceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var deletionDate *timestamppb.Timestamp
|
||||
if !deletedAt.IsZero() {
|
||||
deletionDate = timestamppb.New(deletedAt)
|
||||
}
|
||||
return &action.DeleteTargetResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID),
|
||||
DeletionDate: deletionDate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func createTargetToCommand(req *action.CreateTargetRequest) *command.AddTarget {
|
||||
reqTarget := req.GetTarget()
|
||||
var (
|
||||
targetType domain.TargetType
|
||||
interruptOnError bool
|
||||
)
|
||||
switch t := reqTarget.GetTargetType().(type) {
|
||||
case *action.Target_RestWebhook:
|
||||
switch t := req.GetTargetType().(type) {
|
||||
case *action.CreateTargetRequest_RestWebhook:
|
||||
targetType = domain.TargetTypeWebhook
|
||||
interruptOnError = t.RestWebhook.InterruptOnError
|
||||
case *action.Target_RestCall:
|
||||
case *action.CreateTargetRequest_RestCall:
|
||||
targetType = domain.TargetTypeCall
|
||||
interruptOnError = t.RestCall.InterruptOnError
|
||||
case *action.Target_RestAsync:
|
||||
case *action.CreateTargetRequest_RestAsync:
|
||||
targetType = domain.TargetTypeAsync
|
||||
}
|
||||
return &command.AddTarget{
|
||||
Name: reqTarget.GetName(),
|
||||
Name: req.GetName(),
|
||||
TargetType: targetType,
|
||||
Endpoint: reqTarget.GetEndpoint(),
|
||||
Timeout: reqTarget.GetTimeout().AsDuration(),
|
||||
Endpoint: req.GetEndpoint(),
|
||||
Timeout: req.GetTimeout().AsDuration(),
|
||||
InterruptOnError: interruptOnError,
|
||||
}
|
||||
}
|
||||
|
||||
func patchTargetToCommand(req *action.PatchTargetRequest) *command.ChangeTarget {
|
||||
func updateTargetToCommand(req *action.UpdateTargetRequest) *command.ChangeTarget {
|
||||
expirationSigningKey := false
|
||||
// TODO handle expiration, currently only immediate expiration is supported
|
||||
if req.GetTarget().GetExpirationSigningKey() != nil {
|
||||
if req.GetExpirationSigningKey() != nil {
|
||||
expirationSigningKey = true
|
||||
}
|
||||
|
||||
reqTarget := req.GetTarget()
|
||||
if reqTarget == nil {
|
||||
if req == nil {
|
||||
return nil
|
||||
}
|
||||
target := &command.ChangeTarget{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: req.GetId(),
|
||||
},
|
||||
Name: reqTarget.Name,
|
||||
Endpoint: reqTarget.Endpoint,
|
||||
Name: req.Name,
|
||||
Endpoint: req.Endpoint,
|
||||
ExpirationSigningKey: expirationSigningKey,
|
||||
}
|
||||
if reqTarget.TargetType != nil {
|
||||
switch t := reqTarget.GetTargetType().(type) {
|
||||
case *action.PatchTarget_RestWebhook:
|
||||
if req.TargetType != nil {
|
||||
switch t := req.GetTargetType().(type) {
|
||||
case *action.UpdateTargetRequest_RestWebhook:
|
||||
target.TargetType = gu.Ptr(domain.TargetTypeWebhook)
|
||||
target.InterruptOnError = gu.Ptr(t.RestWebhook.InterruptOnError)
|
||||
case *action.PatchTarget_RestCall:
|
||||
case *action.UpdateTargetRequest_RestCall:
|
||||
target.TargetType = gu.Ptr(domain.TargetTypeCall)
|
||||
target.InterruptOnError = gu.Ptr(t.RestCall.InterruptOnError)
|
||||
case *action.PatchTarget_RestAsync:
|
||||
case *action.UpdateTargetRequest_RestAsync:
|
||||
target.TargetType = gu.Ptr(domain.TargetTypeAsync)
|
||||
target.InterruptOnError = gu.Ptr(false)
|
||||
}
|
||||
}
|
||||
if reqTarget.Timeout != nil {
|
||||
target.Timeout = gu.Ptr(reqTarget.GetTimeout().AsDuration())
|
||||
if req.Timeout != nil {
|
||||
target.Timeout = gu.Ptr(req.GetTimeout().AsDuration())
|
||||
}
|
||||
return target
|
||||
}
|
@@ -10,12 +10,12 @@ import (
|
||||
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/action/v2beta"
|
||||
)
|
||||
|
||||
func Test_createTargetToCommand(t *testing.T) {
|
||||
type args struct {
|
||||
req *action.Target
|
||||
req *action.CreateTargetRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -34,11 +34,11 @@ func Test_createTargetToCommand(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "all fields (webhook)",
|
||||
args: args{&action.Target{
|
||||
args: args{&action.CreateTargetRequest{
|
||||
Name: "target 1",
|
||||
Endpoint: "https://example.com/hooks/1",
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{},
|
||||
TargetType: &action.CreateTargetRequest_RestWebhook{
|
||||
RestWebhook: &action.RESTWebhook{},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
}},
|
||||
@@ -52,11 +52,11 @@ func Test_createTargetToCommand(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "all fields (async)",
|
||||
args: args{&action.Target{
|
||||
args: args{&action.CreateTargetRequest{
|
||||
Name: "target 1",
|
||||
Endpoint: "https://example.com/hooks/1",
|
||||
TargetType: &action.Target_RestAsync{
|
||||
RestAsync: &action.SetRESTAsync{},
|
||||
TargetType: &action.CreateTargetRequest_RestAsync{
|
||||
RestAsync: &action.RESTAsync{},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
}},
|
||||
@@ -70,11 +70,11 @@ func Test_createTargetToCommand(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "all fields (interrupting response)",
|
||||
args: args{&action.Target{
|
||||
args: args{&action.CreateTargetRequest{
|
||||
Name: "target 1",
|
||||
Endpoint: "https://example.com/hooks/1",
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
TargetType: &action.CreateTargetRequest_RestCall{
|
||||
RestCall: &action.RESTCall{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
@@ -91,7 +91,7 @@ func Test_createTargetToCommand(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := createTargetToCommand(&action.CreateTargetRequest{Target: tt.args.req})
|
||||
got := createTargetToCommand(tt.args.req)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
@@ -99,7 +99,7 @@ func Test_createTargetToCommand(t *testing.T) {
|
||||
|
||||
func Test_updateTargetToCommand(t *testing.T) {
|
||||
type args struct {
|
||||
req *action.PatchTarget
|
||||
req *action.UpdateTargetRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -113,7 +113,7 @@ func Test_updateTargetToCommand(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "all fields nil",
|
||||
args: args{&action.PatchTarget{
|
||||
args: args{&action.UpdateTargetRequest{
|
||||
Name: nil,
|
||||
TargetType: nil,
|
||||
Timeout: nil,
|
||||
@@ -128,7 +128,7 @@ func Test_updateTargetToCommand(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "all fields empty",
|
||||
args: args{&action.PatchTarget{
|
||||
args: args{&action.UpdateTargetRequest{
|
||||
Name: gu.Ptr(""),
|
||||
TargetType: nil,
|
||||
Timeout: durationpb.New(0),
|
||||
@@ -143,11 +143,11 @@ func Test_updateTargetToCommand(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "all fields (webhook)",
|
||||
args: args{&action.PatchTarget{
|
||||
args: args{&action.UpdateTargetRequest{
|
||||
Name: gu.Ptr("target 1"),
|
||||
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||
TargetType: &action.PatchTarget_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
TargetType: &action.UpdateTargetRequest_RestWebhook{
|
||||
RestWebhook: &action.RESTWebhook{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
@@ -163,11 +163,11 @@ func Test_updateTargetToCommand(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "all fields (webhook interrupt)",
|
||||
args: args{&action.PatchTarget{
|
||||
args: args{&action.UpdateTargetRequest{
|
||||
Name: gu.Ptr("target 1"),
|
||||
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||
TargetType: &action.PatchTarget_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
TargetType: &action.UpdateTargetRequest_RestWebhook{
|
||||
RestWebhook: &action.RESTWebhook{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
@@ -183,11 +183,11 @@ func Test_updateTargetToCommand(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "all fields (async)",
|
||||
args: args{&action.PatchTarget{
|
||||
args: args{&action.UpdateTargetRequest{
|
||||
Name: gu.Ptr("target 1"),
|
||||
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||
TargetType: &action.PatchTarget_RestAsync{
|
||||
RestAsync: &action.SetRESTAsync{},
|
||||
TargetType: &action.UpdateTargetRequest_RestAsync{
|
||||
RestAsync: &action.RESTAsync{},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
}},
|
||||
@@ -201,11 +201,11 @@ func Test_updateTargetToCommand(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "all fields (interrupting response)",
|
||||
args: args{&action.PatchTarget{
|
||||
args: args{&action.UpdateTargetRequest{
|
||||
Name: gu.Ptr("target 1"),
|
||||
Endpoint: gu.Ptr("https://example.com/hooks/1"),
|
||||
TargetType: &action.PatchTarget_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
TargetType: &action.UpdateTargetRequest_RestCall{
|
||||
RestCall: &action.RESTCall{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
@@ -222,7 +222,7 @@ func Test_updateTargetToCommand(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := patchTargetToCommand(&action.PatchTargetRequest{Target: tt.args.req})
|
||||
got := updateTargetToCommand(tt.args.req)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
@@ -69,7 +69,6 @@ func (s *Server) ListMyUserChanges(ctx context.Context, req *auth_pb.ListMyUserC
|
||||
}
|
||||
|
||||
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
AllowTimeTravel().
|
||||
Limit(limit).
|
||||
OrderDesc().
|
||||
AwaitOpenTransactions().
|
||||
|
@@ -158,14 +158,6 @@ func TestServer_GetSystemFeatures(t *testing.T) {
|
||||
want *feature.GetSystemFeaturesResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "permission error",
|
||||
args: args{
|
||||
ctx: IamCTX,
|
||||
req: &feature.GetSystemFeaturesRequest{},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "nothing set",
|
||||
args: args{
|
||||
@@ -349,14 +341,6 @@ func TestServer_GetInstanceFeatures(t *testing.T) {
|
||||
want *feature.GetInstanceFeaturesResponse
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "permission error",
|
||||
args: args{
|
||||
ctx: OrgCTX,
|
||||
req: &feature.GetInstanceFeaturesRequest{},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "defaults, no inheritance",
|
||||
args: args{
|
||||
|
56
internal/api/grpc/filter/v2beta/converter.go
Normal file
56
internal/api/grpc/filter/v2beta/converter.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package filter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
|
||||
)
|
||||
|
||||
func TextMethodPbToQuery(method filter.TextFilterMethod) query.TextComparison {
|
||||
switch method {
|
||||
case filter.TextFilterMethod_TEXT_FILTER_METHOD_EQUALS:
|
||||
return query.TextEquals
|
||||
case filter.TextFilterMethod_TEXT_FILTER_METHOD_EQUALS_IGNORE_CASE:
|
||||
return query.TextEqualsIgnoreCase
|
||||
case filter.TextFilterMethod_TEXT_FILTER_METHOD_STARTS_WITH:
|
||||
return query.TextStartsWith
|
||||
case filter.TextFilterMethod_TEXT_FILTER_METHOD_STARTS_WITH_IGNORE_CASE:
|
||||
return query.TextStartsWithIgnoreCase
|
||||
case filter.TextFilterMethod_TEXT_FILTER_METHOD_CONTAINS:
|
||||
return query.TextContains
|
||||
case filter.TextFilterMethod_TEXT_FILTER_METHOD_CONTAINS_IGNORE_CASE:
|
||||
return query.TextContainsIgnoreCase
|
||||
case filter.TextFilterMethod_TEXT_FILTER_METHOD_ENDS_WITH:
|
||||
return query.TextEndsWith
|
||||
case filter.TextFilterMethod_TEXT_FILTER_METHOD_ENDS_WITH_IGNORE_CASE:
|
||||
return query.TextEndsWithIgnoreCase
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
func PaginationPbToQuery(defaults systemdefaults.SystemDefaults, query *filter.PaginationRequest) (offset, limit uint64, asc bool, err error) {
|
||||
limit = defaults.DefaultQueryLimit
|
||||
if query == nil {
|
||||
return 0, limit, asc, nil
|
||||
}
|
||||
offset = query.Offset
|
||||
asc = query.Asc
|
||||
if defaults.MaxQueryLimit > 0 && uint64(query.Limit) > defaults.MaxQueryLimit {
|
||||
return 0, 0, false, zerrors.ThrowInvalidArgumentf(fmt.Errorf("given: %d, allowed: %d", query.Limit, defaults.MaxQueryLimit), "QUERY-4M0fs", "Errors.Query.LimitExceeded")
|
||||
}
|
||||
if query.Limit > 0 {
|
||||
limit = uint64(query.Limit)
|
||||
}
|
||||
return offset, limit, asc, nil
|
||||
}
|
||||
|
||||
func QueryToPaginationPb(request query.SearchRequest, response query.SearchResponse) *filter.PaginationResponse {
|
||||
return &filter.PaginationResponse{
|
||||
AppliedLimit: request.Limit,
|
||||
TotalResult: response.Count,
|
||||
}
|
||||
}
|
@@ -50,7 +50,6 @@ func (s *Server) ListOrgChanges(ctx context.Context, req *mgmt_pb.ListOrgChanges
|
||||
}
|
||||
|
||||
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
AllowTimeTravel().
|
||||
Limit(limit).
|
||||
OrderDesc().
|
||||
AwaitOpenTransactions().
|
||||
|
@@ -70,7 +70,6 @@ func (s *Server) ListProjectGrantChanges(ctx context.Context, req *mgmt_pb.ListP
|
||||
}
|
||||
|
||||
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
AllowTimeTravel().
|
||||
Limit(limit).
|
||||
OrderDesc().
|
||||
ResourceOwner(authz.GetCtxData(ctx).OrgID).
|
||||
@@ -152,7 +151,6 @@ func (s *Server) ListProjectChanges(ctx context.Context, req *mgmt_pb.ListProjec
|
||||
}
|
||||
|
||||
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
AllowTimeTravel().
|
||||
Limit(limit).
|
||||
AwaitOpenTransactions().
|
||||
OrderDesc().
|
||||
|
@@ -52,7 +52,6 @@ func (s *Server) ListAppChanges(ctx context.Context, req *mgmt_pb.ListAppChanges
|
||||
}
|
||||
|
||||
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
AllowTimeTravel().
|
||||
Limit(limit).
|
||||
AwaitOpenTransactions().
|
||||
OrderDesc().
|
||||
|
@@ -92,7 +92,6 @@ func (s *Server) ListUserChanges(ctx context.Context, req *mgmt_pb.ListUserChang
|
||||
}
|
||||
|
||||
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
AllowTimeTravel().
|
||||
Limit(limit).
|
||||
AwaitOpenTransactions().
|
||||
OrderDesc().
|
||||
|
@@ -1,499 +0,0 @@
|
||||
//go:build integration
|
||||
|
||||
package action_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"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"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
|
||||
resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha"
|
||||
)
|
||||
|
||||
func TestServer_CreateTarget(t *testing.T) {
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
req *action.Target
|
||||
want *resource_object.Details
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission",
|
||||
ctx: instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||
req: &action.Target{
|
||||
Name: gofakeit.Name(),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty name",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: "",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty type",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: gofakeit.Name(),
|
||||
TargetType: nil,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty webhook url",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: gofakeit.Name(),
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty request response url",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: gofakeit.Name(),
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.SetRESTCall{},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty timeout",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: gofakeit.Name(),
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{},
|
||||
},
|
||||
Timeout: nil,
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "async, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: gofakeit.Name(),
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestAsync{
|
||||
RestAsync: &action.SetRESTAsync{},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "webhook, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: gofakeit.Name(),
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "webhook, interrupt on error, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: gofakeit.Name(),
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestWebhook{
|
||||
RestWebhook: &action.SetRESTWebhook{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "call, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: gofakeit.Name(),
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: false,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
name: "call, interruptOnError, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.Target{
|
||||
Name: gofakeit.Name(),
|
||||
Endpoint: "https://example.com",
|
||||
TargetType: &action.Target_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
Timeout: durationpb.New(10 * time.Second),
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := instance.Client.ActionV3Alpha.CreateTarget(tt.ctx, &action.CreateTargetRequest{Target: tt.req})
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.want, got.Details)
|
||||
assert.NotEmpty(t, got.GetSigningKey())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_PatchTarget(t *testing.T) {
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *action.PatchTargetRequest
|
||||
}
|
||||
type want struct {
|
||||
details *resource_object.Details
|
||||
signingKey bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
prepare func(request *action.PatchTargetRequest) error
|
||||
args args
|
||||
want want
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission",
|
||||
prepare: func(request *action.PatchTargetRequest) error {
|
||||
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||
request.Id = targetID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||
req: &action.PatchTargetRequest{
|
||||
Target: &action.PatchTarget{
|
||||
Name: gu.Ptr(gofakeit.Name()),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "not existing",
|
||||
prepare: func(request *action.PatchTargetRequest) error {
|
||||
request.Id = "notexisting"
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.PatchTargetRequest{
|
||||
Target: &action.PatchTarget{
|
||||
Name: gu.Ptr(gofakeit.Name()),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "change name, ok",
|
||||
prepare: func(request *action.PatchTargetRequest) error {
|
||||
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||
request.Id = targetID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.PatchTargetRequest{
|
||||
Target: &action.PatchTarget{
|
||||
Name: gu.Ptr(gofakeit.Name()),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "regenerate signingkey, ok",
|
||||
prepare: func(request *action.PatchTargetRequest) error {
|
||||
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||
request.Id = targetID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.PatchTargetRequest{
|
||||
Target: &action.PatchTarget{
|
||||
ExpirationSigningKey: durationpb.New(0 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
signingKey: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change type, ok",
|
||||
prepare: func(request *action.PatchTargetRequest) error {
|
||||
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||
request.Id = targetID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.PatchTargetRequest{
|
||||
Target: &action.PatchTarget{
|
||||
TargetType: &action.PatchTarget_RestCall{
|
||||
RestCall: &action.SetRESTCall{
|
||||
InterruptOnError: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change url, ok",
|
||||
prepare: func(request *action.PatchTargetRequest) error {
|
||||
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||
request.Id = targetID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.PatchTargetRequest{
|
||||
Target: &action.PatchTarget{
|
||||
Endpoint: gu.Ptr("https://example.com/hooks/new"),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change timeout, ok",
|
||||
prepare: func(request *action.PatchTargetRequest) error {
|
||||
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeWebhook, false).GetDetails().GetId()
|
||||
request.Id = targetID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.PatchTargetRequest{
|
||||
Target: &action.PatchTarget{
|
||||
Timeout: durationpb.New(20 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "change type async, ok",
|
||||
prepare: func(request *action.PatchTargetRequest) error {
|
||||
targetID := instance.CreateTarget(isolatedIAMOwnerCTX, t, "", "https://example.com", domain.TargetTypeAsync, false).GetDetails().GetId()
|
||||
request.Id = targetID
|
||||
return nil
|
||||
},
|
||||
args: args{
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
req: &action.PatchTargetRequest{
|
||||
Target: &action.PatchTarget{
|
||||
TargetType: &action.PatchTarget_RestAsync{
|
||||
RestAsync: &action.SetRESTAsync{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
details: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := tt.prepare(tt.args.req)
|
||||
require.NoError(t, err)
|
||||
// We want to have the same response no matter how often we call the function
|
||||
instance.Client.ActionV3Alpha.PatchTarget(tt.args.ctx, tt.args.req)
|
||||
got, err := instance.Client.ActionV3Alpha.PatchTarget(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.want.details, got.Details)
|
||||
if tt.want.signingKey {
|
||||
assert.NotEmpty(t, got.SigningKey)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_DeleteTarget(t *testing.T) {
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
iamOwnerCtx := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
target := instance.CreateTarget(iamOwnerCtx, t, "", "https://example.com", domain.TargetTypeWebhook, false)
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
req *action.DeleteTargetRequest
|
||||
want *resource_object.Details
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "missing permission",
|
||||
ctx: instance.WithAuthorization(context.Background(), integration.UserTypeOrgOwner),
|
||||
req: &action.DeleteTargetRequest{
|
||||
Id: target.GetDetails().GetId(),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "empty id",
|
||||
ctx: iamOwnerCtx,
|
||||
req: &action.DeleteTargetRequest{
|
||||
Id: "",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "delete target",
|
||||
ctx: iamOwnerCtx,
|
||||
req: &action.DeleteTargetRequest{
|
||||
Id: target.GetDetails().GetId(),
|
||||
},
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := instance.Client.ActionV3Alpha.DeleteTarget(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.want, got.Details)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -1,173 +0,0 @@
|
||||
package webkey
|
||||
|
||||
import (
|
||||
resource_object "github.com/zitadel/zitadel/internal/api/grpc/resources/object/v3alpha"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
webkey "github.com/zitadel/zitadel/pkg/grpc/resources/webkey/v3alpha"
|
||||
)
|
||||
|
||||
func createWebKeyRequestToConfig(req *webkey.CreateWebKeyRequest) crypto.WebKeyConfig {
|
||||
switch config := req.GetKey().GetConfig().(type) {
|
||||
case *webkey.WebKey_Rsa:
|
||||
return webKeyRSAConfigToCrypto(config.Rsa)
|
||||
case *webkey.WebKey_Ecdsa:
|
||||
return webKeyECDSAConfigToCrypto(config.Ecdsa)
|
||||
case *webkey.WebKey_Ed25519:
|
||||
return new(crypto.WebKeyED25519Config)
|
||||
default:
|
||||
return webKeyRSAConfigToCrypto(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func webKeyRSAConfigToCrypto(config *webkey.WebKeyRSAConfig) *crypto.WebKeyRSAConfig {
|
||||
out := new(crypto.WebKeyRSAConfig)
|
||||
|
||||
switch config.GetBits() {
|
||||
case webkey.WebKeyRSAConfig_RSA_BITS_UNSPECIFIED:
|
||||
out.Bits = crypto.RSABits2048
|
||||
case webkey.WebKeyRSAConfig_RSA_BITS_2048:
|
||||
out.Bits = crypto.RSABits2048
|
||||
case webkey.WebKeyRSAConfig_RSA_BITS_3072:
|
||||
out.Bits = crypto.RSABits3072
|
||||
case webkey.WebKeyRSAConfig_RSA_BITS_4096:
|
||||
out.Bits = crypto.RSABits4096
|
||||
default:
|
||||
out.Bits = crypto.RSABits2048
|
||||
}
|
||||
|
||||
switch config.GetHasher() {
|
||||
case webkey.WebKeyRSAConfig_RSA_HASHER_UNSPECIFIED:
|
||||
out.Hasher = crypto.RSAHasherSHA256
|
||||
case webkey.WebKeyRSAConfig_RSA_HASHER_SHA256:
|
||||
out.Hasher = crypto.RSAHasherSHA256
|
||||
case webkey.WebKeyRSAConfig_RSA_HASHER_SHA384:
|
||||
out.Hasher = crypto.RSAHasherSHA384
|
||||
case webkey.WebKeyRSAConfig_RSA_HASHER_SHA512:
|
||||
out.Hasher = crypto.RSAHasherSHA512
|
||||
default:
|
||||
out.Hasher = crypto.RSAHasherSHA256
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func webKeyECDSAConfigToCrypto(config *webkey.WebKeyECDSAConfig) *crypto.WebKeyECDSAConfig {
|
||||
out := new(crypto.WebKeyECDSAConfig)
|
||||
|
||||
switch config.GetCurve() {
|
||||
case webkey.WebKeyECDSAConfig_ECDSA_CURVE_UNSPECIFIED:
|
||||
out.Curve = crypto.EllipticCurveP256
|
||||
case webkey.WebKeyECDSAConfig_ECDSA_CURVE_P256:
|
||||
out.Curve = crypto.EllipticCurveP256
|
||||
case webkey.WebKeyECDSAConfig_ECDSA_CURVE_P384:
|
||||
out.Curve = crypto.EllipticCurveP384
|
||||
case webkey.WebKeyECDSAConfig_ECDSA_CURVE_P512:
|
||||
out.Curve = crypto.EllipticCurveP512
|
||||
default:
|
||||
out.Curve = crypto.EllipticCurveP256
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func webKeyDetailsListToPb(list []query.WebKeyDetails, instanceID string) []*webkey.GetWebKey {
|
||||
out := make([]*webkey.GetWebKey, len(list))
|
||||
for i := range list {
|
||||
out[i] = webKeyDetailsToPb(&list[i], instanceID)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func webKeyDetailsToPb(details *query.WebKeyDetails, instanceID string) *webkey.GetWebKey {
|
||||
out := &webkey.GetWebKey{
|
||||
Details: resource_object.DomainToDetailsPb(&domain.ObjectDetails{
|
||||
ID: details.KeyID,
|
||||
CreationDate: details.CreationDate,
|
||||
EventDate: details.ChangeDate,
|
||||
}, object.OwnerType_OWNER_TYPE_INSTANCE, instanceID),
|
||||
State: webKeyStateToPb(details.State),
|
||||
Config: &webkey.WebKey{},
|
||||
}
|
||||
|
||||
switch config := details.Config.(type) {
|
||||
case *crypto.WebKeyRSAConfig:
|
||||
out.Config.Config = &webkey.WebKey_Rsa{
|
||||
Rsa: webKeyRSAConfigToPb(config),
|
||||
}
|
||||
case *crypto.WebKeyECDSAConfig:
|
||||
out.Config.Config = &webkey.WebKey_Ecdsa{
|
||||
Ecdsa: webKeyECDSAConfigToPb(config),
|
||||
}
|
||||
case *crypto.WebKeyED25519Config:
|
||||
out.Config.Config = &webkey.WebKey_Ed25519{
|
||||
Ed25519: new(webkey.WebKeyED25519Config),
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func webKeyStateToPb(state domain.WebKeyState) webkey.WebKeyState {
|
||||
switch state {
|
||||
case domain.WebKeyStateUnspecified:
|
||||
return webkey.WebKeyState_STATE_UNSPECIFIED
|
||||
case domain.WebKeyStateInitial:
|
||||
return webkey.WebKeyState_STATE_INITIAL
|
||||
case domain.WebKeyStateActive:
|
||||
return webkey.WebKeyState_STATE_ACTIVE
|
||||
case domain.WebKeyStateInactive:
|
||||
return webkey.WebKeyState_STATE_INACTIVE
|
||||
case domain.WebKeyStateRemoved:
|
||||
return webkey.WebKeyState_STATE_REMOVED
|
||||
default:
|
||||
return webkey.WebKeyState_STATE_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func webKeyRSAConfigToPb(config *crypto.WebKeyRSAConfig) *webkey.WebKeyRSAConfig {
|
||||
out := new(webkey.WebKeyRSAConfig)
|
||||
|
||||
switch config.Bits {
|
||||
case crypto.RSABitsUnspecified:
|
||||
out.Bits = webkey.WebKeyRSAConfig_RSA_BITS_UNSPECIFIED
|
||||
case crypto.RSABits2048:
|
||||
out.Bits = webkey.WebKeyRSAConfig_RSA_BITS_2048
|
||||
case crypto.RSABits3072:
|
||||
out.Bits = webkey.WebKeyRSAConfig_RSA_BITS_3072
|
||||
case crypto.RSABits4096:
|
||||
out.Bits = webkey.WebKeyRSAConfig_RSA_BITS_4096
|
||||
}
|
||||
|
||||
switch config.Hasher {
|
||||
case crypto.RSAHasherUnspecified:
|
||||
out.Hasher = webkey.WebKeyRSAConfig_RSA_HASHER_UNSPECIFIED
|
||||
case crypto.RSAHasherSHA256:
|
||||
out.Hasher = webkey.WebKeyRSAConfig_RSA_HASHER_SHA256
|
||||
case crypto.RSAHasherSHA384:
|
||||
out.Hasher = webkey.WebKeyRSAConfig_RSA_HASHER_SHA384
|
||||
case crypto.RSAHasherSHA512:
|
||||
out.Hasher = webkey.WebKeyRSAConfig_RSA_HASHER_SHA512
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func webKeyECDSAConfigToPb(config *crypto.WebKeyECDSAConfig) *webkey.WebKeyECDSAConfig {
|
||||
out := new(webkey.WebKeyECDSAConfig)
|
||||
|
||||
switch config.Curve {
|
||||
case crypto.EllipticCurveUnspecified:
|
||||
out.Curve = webkey.WebKeyECDSAConfig_ECDSA_CURVE_UNSPECIFIED
|
||||
case crypto.EllipticCurveP256:
|
||||
out.Curve = webkey.WebKeyECDSAConfig_ECDSA_CURVE_P256
|
||||
case crypto.EllipticCurveP384:
|
||||
out.Curve = webkey.WebKeyECDSAConfig_ECDSA_CURVE_P384
|
||||
case crypto.EllipticCurveP512:
|
||||
out.Curve = webkey.WebKeyECDSAConfig_ECDSA_CURVE_P512
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
@@ -13,13 +13,13 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
)
|
||||
|
||||
func AuthorizationInterceptor(verifier authz.APITokenVerifier, authConfig authz.Config) grpc.UnaryServerInterceptor {
|
||||
func AuthorizationInterceptor(verifier authz.APITokenVerifier, systemUserPermissions authz.Config, authConfig authz.Config) grpc.UnaryServerInterceptor {
|
||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
return authorize(ctx, req, info, handler, verifier, authConfig)
|
||||
return authorize(ctx, req, info, handler, verifier, systemUserPermissions, authConfig)
|
||||
}
|
||||
}
|
||||
|
||||
func authorize(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, verifier authz.APITokenVerifier, authConfig authz.Config) (_ interface{}, err error) {
|
||||
func authorize(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler, verifier authz.APITokenVerifier, systemUserPermissions authz.Config, authConfig authz.Config) (_ interface{}, err error) {
|
||||
authOpt, needsToken := verifier.CheckAuthMethod(info.FullMethod)
|
||||
if !needsToken {
|
||||
return handler(ctx, req)
|
||||
@@ -34,7 +34,7 @@ func authorize(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo,
|
||||
}
|
||||
|
||||
orgID, orgDomain := orgIDAndDomainFromRequest(authCtx, req)
|
||||
ctxSetter, err := authz.CheckUserAuthorization(authCtx, req, authToken, orgID, orgDomain, verifier, authConfig, authOpt, info.FullMethod)
|
||||
ctxSetter, err := authz.CheckUserAuthorization(authCtx, req, authToken, orgID, orgDomain, verifier, systemUserPermissions.RolePermissionMappings, authConfig.RolePermissionMappings, authOpt, info.FullMethod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -20,6 +20,7 @@ type authzRepoMock struct{}
|
||||
func (v *authzRepoMock) VerifyAccessToken(ctx context.Context, token, clientID, projectID string) (string, string, string, string, string, error) {
|
||||
return "", "", "", "", "", nil
|
||||
}
|
||||
|
||||
func (v *authzRepoMock) SearchMyMemberships(ctx context.Context, orgID string, _ bool) ([]*authz.Membership, error) {
|
||||
return authz.Memberships{{
|
||||
MemberType: authz.MemberTypeOrganization,
|
||||
@@ -31,9 +32,11 @@ func (v *authzRepoMock) SearchMyMemberships(ctx context.Context, orgID string, _
|
||||
func (v *authzRepoMock) ProjectIDAndOriginsByClientID(ctx context.Context, clientID string) (string, []string, error) {
|
||||
return "", nil, nil
|
||||
}
|
||||
|
||||
func (v *authzRepoMock) ExistsOrg(ctx context.Context, orgID, domain string) (string, error) {
|
||||
return orgID, nil
|
||||
}
|
||||
|
||||
func (v *authzRepoMock) VerifierClientID(ctx context.Context, appName string) (string, string, error) {
|
||||
return "", "", nil
|
||||
}
|
||||
@@ -252,7 +255,7 @@ func Test_authorize(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := authorize(tt.args.ctx, tt.args.req, tt.args.info, tt.args.handler, tt.args.verifier(), tt.args.authConfig)
|
||||
got, err := authorize(tt.args.ctx, tt.args.req, tt.args.info, tt.args.handler, tt.args.verifier(), tt.args.authConfig, tt.args.authConfig)
|
||||
if (err != nil) != tt.res.wantErr {
|
||||
t.Errorf("authorize() error = %v, wantErr %v", err, tt.res.wantErr)
|
||||
return
|
||||
|
@@ -36,6 +36,7 @@ type WithGatewayPrefix interface {
|
||||
|
||||
func CreateServer(
|
||||
verifier authz.APITokenVerifier,
|
||||
systemAuthz authz.Config,
|
||||
authConfig authz.Config,
|
||||
queries *query.Queries,
|
||||
externalDomain string,
|
||||
@@ -53,7 +54,7 @@ func CreateServer(
|
||||
middleware.AccessStorageInterceptor(accessSvc),
|
||||
middleware.ErrorHandler(),
|
||||
middleware.LimitsInterceptor(system_pb.SystemService_ServiceDesc.ServiceName),
|
||||
middleware.AuthorizationInterceptor(verifier, authConfig),
|
||||
middleware.AuthorizationInterceptor(verifier, systemAuthz, authConfig),
|
||||
middleware.TranslationHandler(),
|
||||
middleware.QuotaExhaustedInterceptor(accessSvc, system_pb.SystemService_ServiceDesc.ServiceName),
|
||||
middleware.ExecutionHandler(queries),
|
||||
|
@@ -73,6 +73,7 @@ func requireEventually(
|
||||
assertCounts func(assert.TestingT, *eventCounts),
|
||||
msg string,
|
||||
) (counts *eventCounts) {
|
||||
t.Helper()
|
||||
countTimeout := 30 * time.Second
|
||||
assertTimeout := countTimeout + time.Second
|
||||
countCtx, cancel := context.WithTimeout(ctx, time.Minute)
|
||||
|
@@ -415,6 +415,10 @@ func createUsers(ctx context.Context, orgID string, count int, passwordChangeReq
|
||||
|
||||
func createUser(ctx context.Context, orgID string, passwordChangeRequired bool) userAttr {
|
||||
username := gofakeit.Email()
|
||||
return createUserWithUserName(ctx, username, orgID, passwordChangeRequired)
|
||||
}
|
||||
|
||||
func createUserWithUserName(ctx context.Context, username string, orgID string, passwordChangeRequired bool) userAttr {
|
||||
// used as default country prefix
|
||||
phone := "+41" + gofakeit.Phone()
|
||||
resp := Instance.CreateHumanUserVerified(ctx, orgID, username, phone)
|
||||
@@ -1179,6 +1183,97 @@ func TestServer_ListUsers(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_SystemUsers_ListUsers(t *testing.T) {
|
||||
defer func() {
|
||||
_, err := Instance.Client.FeatureV2.ResetInstanceFeatures(IamCTX, &feature.ResetInstanceFeaturesRequest{})
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
org1 := Instance.CreateOrganization(IamCTX, fmt.Sprintf("ListUsersOrg-%s", gofakeit.AppName()), gofakeit.Email())
|
||||
org2 := Instance.CreateOrganization(IamCTX, fmt.Sprintf("ListUsersOrg-%s", gofakeit.AppName()), "org2@zitadel.com")
|
||||
org3 := Instance.CreateOrganization(IamCTX, fmt.Sprintf("ListUsersOrg-%s", gofakeit.AppName()), gofakeit.Email())
|
||||
_ = createUserWithUserName(IamCTX, "Test_SystemUsers_ListUser1@zitadel.com", org1.OrganizationId, false)
|
||||
_ = createUserWithUserName(IamCTX, "Test_SystemUsers_ListUser2@zitadel.com", org2.OrganizationId, false)
|
||||
_ = createUserWithUserName(IamCTX, "Test_SystemUsers_ListUser3@zitadel.com", org3.OrganizationId, false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
req *user.ListUsersRequest
|
||||
expectedFoundUsernames []string
|
||||
checkNumberOfUsersReturned bool
|
||||
}{
|
||||
{
|
||||
name: "list users with neccessary permissions",
|
||||
ctx: SystemCTX,
|
||||
req: &user.ListUsersRequest{},
|
||||
// the number of users returned will vary from test run to test run,
|
||||
// so just check the system user gets back users from different orgs whcih it is not a memeber of
|
||||
checkNumberOfUsersReturned: false,
|
||||
expectedFoundUsernames: []string{"Test_SystemUsers_ListUser1@zitadel.com", "Test_SystemUsers_ListUser2@zitadel.com", "Test_SystemUsers_ListUser3@zitadel.com"},
|
||||
},
|
||||
{
|
||||
name: "list users without neccessary permissions",
|
||||
ctx: SystemUserWithNoPermissionsCTX,
|
||||
req: &user.ListUsersRequest{},
|
||||
// check no users returned
|
||||
checkNumberOfUsersReturned: true,
|
||||
},
|
||||
{
|
||||
name: "list users with neccessary permissions specifying org",
|
||||
req: &user.ListUsersRequest{
|
||||
Queries: []*user.SearchQuery{OrganizationIdQuery(org2.OrganizationId)},
|
||||
},
|
||||
ctx: SystemCTX,
|
||||
expectedFoundUsernames: []string{"Test_SystemUsers_ListUser2@zitadel.com", "org2@zitadel.com"},
|
||||
checkNumberOfUsersReturned: true,
|
||||
},
|
||||
{
|
||||
name: "list users without neccessary permissions specifying org",
|
||||
req: &user.ListUsersRequest{
|
||||
Queries: []*user.SearchQuery{OrganizationIdQuery(org2.OrganizationId)},
|
||||
},
|
||||
ctx: SystemUserWithNoPermissionsCTX,
|
||||
// check no users returned
|
||||
checkNumberOfUsersReturned: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, f := range permissionCheckV2Settings {
|
||||
f := f
|
||||
for _, tt := range tests {
|
||||
t.Run(f.TestNamePrependString+tt.name, func(t *testing.T) {
|
||||
setPermissionCheckV2Flag(t, f.SetFlag)
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tt.ctx, 1*time.Minute)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
got, err := Client.ListUsers(tt.ctx, tt.req)
|
||||
require.NoError(ttt, err)
|
||||
|
||||
if tt.checkNumberOfUsersReturned {
|
||||
require.Equal(t, len(tt.expectedFoundUsernames), len(got.Result))
|
||||
}
|
||||
|
||||
if tt.expectedFoundUsernames != nil {
|
||||
for _, user := range got.Result {
|
||||
for i, username := range tt.expectedFoundUsernames {
|
||||
if username == user.Username {
|
||||
tt.expectedFoundUsernames = tt.expectedFoundUsernames[i+1:]
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(tt.expectedFoundUsernames) == 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
require.FailNow(t, "unable to find all users with specified usernames")
|
||||
}
|
||||
}, retryDuration, tick, "timeout waiting for expected user result")
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func InUserIDsQuery(ids []string) *user.SearchQuery {
|
||||
return &user.SearchQuery{
|
||||
Query: &user.SearchQuery_InUserIdsQuery{
|
||||
|
@@ -31,12 +31,13 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
CTX context.Context
|
||||
IamCTX context.Context
|
||||
UserCTX context.Context
|
||||
SystemCTX context.Context
|
||||
Instance *integration.Instance
|
||||
Client user.UserServiceClient
|
||||
CTX context.Context
|
||||
IamCTX context.Context
|
||||
UserCTX context.Context
|
||||
SystemCTX context.Context
|
||||
SystemUserWithNoPermissionsCTX context.Context
|
||||
Instance *integration.Instance
|
||||
Client user.UserServiceClient
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@@ -46,6 +47,7 @@ func TestMain(m *testing.M) {
|
||||
|
||||
Instance = integration.NewInstance(ctx)
|
||||
|
||||
SystemUserWithNoPermissionsCTX = integration.WithSystemUserWithNoPermissionsAuthorization(ctx)
|
||||
UserCTX = Instance.WithAuthorization(ctx, integration.UserTypeNoPermission)
|
||||
IamCTX = Instance.WithAuthorization(ctx, integration.UserTypeIAMOwner)
|
||||
SystemCTX = integration.WithSystemAuthorization(ctx)
|
||||
@@ -1306,7 +1308,6 @@ func TestServer_UpdateHumanUser_Permission(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
got, err := Client.UpdateHumanUser(tt.args.ctx, tt.args.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
@@ -3048,7 +3049,6 @@ func TestServer_ListAuthenticationFactors(t *testing.T) {
|
||||
|
||||
assert.ElementsMatch(t, tt.want.GetResult(), got.GetResult())
|
||||
}, retryDuration, tick, "timeout waiting for expected auth methods result")
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@@ -17,9 +17,7 @@ import (
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha"
|
||||
webkey "github.com/zitadel/zitadel/pkg/grpc/resources/webkey/v3alpha"
|
||||
webkey "github.com/zitadel/zitadel/pkg/grpc/webkey/v2beta"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -37,7 +35,7 @@ func TestMain(m *testing.M) {
|
||||
|
||||
func TestServer_Feature_Disabled(t *testing.T) {
|
||||
instance, iamCtx, _ := createInstance(t, false)
|
||||
client := instance.Client.WebKeyV3Alpha
|
||||
client := instance.Client.WebKeyV2Beta
|
||||
|
||||
t.Run("CreateWebKey", func(t *testing.T) {
|
||||
_, err := client.CreateWebKey(iamCtx, &webkey.CreateWebKeyRequest{})
|
||||
@@ -65,84 +63,78 @@ func TestServer_ListWebKeys(t *testing.T) {
|
||||
instance, iamCtx, creationDate := createInstance(t, true)
|
||||
// After the feature is first enabled, we can expect 2 generated keys with the default config.
|
||||
checkWebKeyListState(iamCtx, t, instance, 2, "", &webkey.WebKey_Rsa{
|
||||
Rsa: &webkey.WebKeyRSAConfig{
|
||||
Bits: webkey.WebKeyRSAConfig_RSA_BITS_2048,
|
||||
Hasher: webkey.WebKeyRSAConfig_RSA_HASHER_SHA256,
|
||||
Rsa: &webkey.RSA{
|
||||
Bits: webkey.RSABits_RSA_BITS_2048,
|
||||
Hasher: webkey.RSAHasher_RSA_HASHER_SHA256,
|
||||
},
|
||||
}, creationDate)
|
||||
}
|
||||
|
||||
func TestServer_CreateWebKey(t *testing.T) {
|
||||
instance, iamCtx, creationDate := createInstance(t, true)
|
||||
client := instance.Client.WebKeyV3Alpha
|
||||
client := instance.Client.WebKeyV2Beta
|
||||
|
||||
_, err := client.CreateWebKey(iamCtx, &webkey.CreateWebKeyRequest{
|
||||
Key: &webkey.WebKey{
|
||||
Config: &webkey.WebKey_Rsa{
|
||||
Rsa: &webkey.WebKeyRSAConfig{
|
||||
Bits: webkey.WebKeyRSAConfig_RSA_BITS_2048,
|
||||
Hasher: webkey.WebKeyRSAConfig_RSA_HASHER_SHA256,
|
||||
},
|
||||
Key: &webkey.CreateWebKeyRequest_Rsa{
|
||||
Rsa: &webkey.RSA{
|
||||
Bits: webkey.RSABits_RSA_BITS_2048,
|
||||
Hasher: webkey.RSAHasher_RSA_HASHER_SHA256,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
checkWebKeyListState(iamCtx, t, instance, 3, "", &webkey.WebKey_Rsa{
|
||||
Rsa: &webkey.WebKeyRSAConfig{
|
||||
Bits: webkey.WebKeyRSAConfig_RSA_BITS_2048,
|
||||
Hasher: webkey.WebKeyRSAConfig_RSA_HASHER_SHA256,
|
||||
Rsa: &webkey.RSA{
|
||||
Bits: webkey.RSABits_RSA_BITS_2048,
|
||||
Hasher: webkey.RSAHasher_RSA_HASHER_SHA256,
|
||||
},
|
||||
}, creationDate)
|
||||
}
|
||||
|
||||
func TestServer_ActivateWebKey(t *testing.T) {
|
||||
instance, iamCtx, creationDate := createInstance(t, true)
|
||||
client := instance.Client.WebKeyV3Alpha
|
||||
client := instance.Client.WebKeyV2Beta
|
||||
|
||||
resp, err := client.CreateWebKey(iamCtx, &webkey.CreateWebKeyRequest{
|
||||
Key: &webkey.WebKey{
|
||||
Config: &webkey.WebKey_Rsa{
|
||||
Rsa: &webkey.WebKeyRSAConfig{
|
||||
Bits: webkey.WebKeyRSAConfig_RSA_BITS_2048,
|
||||
Hasher: webkey.WebKeyRSAConfig_RSA_HASHER_SHA256,
|
||||
},
|
||||
Key: &webkey.CreateWebKeyRequest_Rsa{
|
||||
Rsa: &webkey.RSA{
|
||||
Bits: webkey.RSABits_RSA_BITS_2048,
|
||||
Hasher: webkey.RSAHasher_RSA_HASHER_SHA256,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = client.ActivateWebKey(iamCtx, &webkey.ActivateWebKeyRequest{
|
||||
Id: resp.GetDetails().GetId(),
|
||||
Id: resp.GetId(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
checkWebKeyListState(iamCtx, t, instance, 3, resp.GetDetails().GetId(), &webkey.WebKey_Rsa{
|
||||
Rsa: &webkey.WebKeyRSAConfig{
|
||||
Bits: webkey.WebKeyRSAConfig_RSA_BITS_2048,
|
||||
Hasher: webkey.WebKeyRSAConfig_RSA_HASHER_SHA256,
|
||||
checkWebKeyListState(iamCtx, t, instance, 3, resp.GetId(), &webkey.WebKey_Rsa{
|
||||
Rsa: &webkey.RSA{
|
||||
Bits: webkey.RSABits_RSA_BITS_2048,
|
||||
Hasher: webkey.RSAHasher_RSA_HASHER_SHA256,
|
||||
},
|
||||
}, creationDate)
|
||||
}
|
||||
|
||||
func TestServer_DeleteWebKey(t *testing.T) {
|
||||
instance, iamCtx, creationDate := createInstance(t, true)
|
||||
client := instance.Client.WebKeyV3Alpha
|
||||
client := instance.Client.WebKeyV2Beta
|
||||
|
||||
keyIDs := make([]string, 2)
|
||||
for i := 0; i < 2; i++ {
|
||||
resp, err := client.CreateWebKey(iamCtx, &webkey.CreateWebKeyRequest{
|
||||
Key: &webkey.WebKey{
|
||||
Config: &webkey.WebKey_Rsa{
|
||||
Rsa: &webkey.WebKeyRSAConfig{
|
||||
Bits: webkey.WebKeyRSAConfig_RSA_BITS_2048,
|
||||
Hasher: webkey.WebKeyRSAConfig_RSA_HASHER_SHA256,
|
||||
},
|
||||
Key: &webkey.CreateWebKeyRequest_Rsa{
|
||||
Rsa: &webkey.RSA{
|
||||
Bits: webkey.RSABits_RSA_BITS_2048,
|
||||
Hasher: webkey.RSAHasher_RSA_HASHER_SHA256,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
keyIDs[i] = resp.GetDetails().GetId()
|
||||
keyIDs[i] = resp.GetId()
|
||||
}
|
||||
_, err := client.ActivateWebKey(iamCtx, &webkey.ActivateWebKeyRequest{
|
||||
Id: keyIDs[0],
|
||||
@@ -162,11 +154,35 @@ func TestServer_DeleteWebKey(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
ok = t.Run("delete inactive key", func(t *testing.T) {
|
||||
_, err := client.DeleteWebKey(iamCtx, &webkey.DeleteWebKeyRequest{
|
||||
resp, err := client.DeleteWebKey(iamCtx, &webkey.DeleteWebKeyRequest{
|
||||
Id: keyIDs[1],
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.WithinRange(t, resp.GetDeletionDate().AsTime(), start, time.Now())
|
||||
})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
ok = t.Run("delete inactive key again", func(t *testing.T) {
|
||||
resp, err := client.DeleteWebKey(iamCtx, &webkey.DeleteWebKeyRequest{
|
||||
Id: keyIDs[1],
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.WithinRange(t, resp.GetDeletionDate().AsTime(), start, time.Now())
|
||||
})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
ok = t.Run("delete not existing key", func(t *testing.T) {
|
||||
resp, err := client.DeleteWebKey(iamCtx, &webkey.DeleteWebKeyRequest{
|
||||
Id: "not-existing",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, resp.DeletionDate)
|
||||
})
|
||||
if !ok {
|
||||
return
|
||||
@@ -174,9 +190,9 @@ func TestServer_DeleteWebKey(t *testing.T) {
|
||||
|
||||
// There are 2 keys from feature setup, +2 created, -1 deleted = 3
|
||||
checkWebKeyListState(iamCtx, t, instance, 3, keyIDs[0], &webkey.WebKey_Rsa{
|
||||
Rsa: &webkey.WebKeyRSAConfig{
|
||||
Bits: webkey.WebKeyRSAConfig_RSA_BITS_2048,
|
||||
Hasher: webkey.WebKeyRSAConfig_RSA_HASHER_SHA256,
|
||||
Rsa: &webkey.RSA{
|
||||
Bits: webkey.RSABits_RSA_BITS_2048,
|
||||
Hasher: webkey.RSAHasher_RSA_HASHER_SHA256,
|
||||
},
|
||||
}, creationDate)
|
||||
}
|
||||
@@ -195,7 +211,7 @@ func createInstance(t *testing.T, enableFeature bool) (*integration.Instance, co
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(iamCTX, time.Minute)
|
||||
assert.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
resp, err := instance.Client.WebKeyV3Alpha.ListWebKeys(iamCTX, &webkey.ListWebKeysRequest{})
|
||||
resp, err := instance.Client.WebKeyV2Beta.ListWebKeys(iamCTX, &webkey.ListWebKeysRequest{})
|
||||
if enableFeature {
|
||||
assert.NoError(collect, err)
|
||||
assert.Len(collect, resp.GetWebKeys(), 2)
|
||||
@@ -220,7 +236,7 @@ func checkWebKeyListState(ctx context.Context, t *testing.T, instance *integrati
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(ctx, time.Minute)
|
||||
assert.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
resp, err := instance.Client.WebKeyV3Alpha.ListWebKeys(ctx, &webkey.ListWebKeysRequest{})
|
||||
resp, err := instance.Client.WebKeyV2Beta.ListWebKeys(ctx, &webkey.ListWebKeysRequest{})
|
||||
require.NoError(collect, err)
|
||||
list := resp.GetWebKeys()
|
||||
assert.Len(collect, list, nKeys)
|
||||
@@ -228,21 +244,14 @@ func checkWebKeyListState(ctx context.Context, t *testing.T, instance *integrati
|
||||
now := time.Now()
|
||||
var gotActiveKeyID string
|
||||
for _, key := range list {
|
||||
integration.AssertResourceDetails(t, &resource_object.Details{
|
||||
Created: creationDate,
|
||||
Changed: creationDate,
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_INSTANCE,
|
||||
Id: instance.ID(),
|
||||
},
|
||||
}, key.GetDetails())
|
||||
assert.WithinRange(collect, key.GetDetails().GetChanged().AsTime(), now.Add(-time.Minute), now.Add(time.Minute))
|
||||
assert.NotEqual(collect, webkey.WebKeyState_STATE_UNSPECIFIED, key.GetState())
|
||||
assert.NotEqual(collect, webkey.WebKeyState_STATE_REMOVED, key.GetState())
|
||||
assert.Equal(collect, config, key.GetConfig().GetConfig())
|
||||
assert.WithinRange(collect, key.GetCreationDate().AsTime(), now.Add(-time.Minute), now.Add(time.Minute))
|
||||
assert.WithinRange(collect, key.GetChangeDate().AsTime(), now.Add(-time.Minute), now.Add(time.Minute))
|
||||
assert.NotEqual(collect, webkey.State_STATE_UNSPECIFIED, key.GetState())
|
||||
assert.NotEqual(collect, webkey.State_STATE_REMOVED, key.GetState())
|
||||
assert.Equal(collect, config, key.GetKey())
|
||||
|
||||
if key.GetState() == webkey.WebKeyState_STATE_ACTIVE {
|
||||
gotActiveKeyID = key.GetDetails().GetId()
|
||||
if key.GetState() == webkey.State_STATE_ACTIVE {
|
||||
gotActiveKeyID = key.GetId()
|
||||
}
|
||||
}
|
||||
assert.NotEmpty(collect, gotActiveKeyID)
|
@@ -7,11 +7,11 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
webkey "github.com/zitadel/zitadel/pkg/grpc/resources/webkey/v3alpha"
|
||||
webkey "github.com/zitadel/zitadel/pkg/grpc/webkey/v2beta"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
webkey.UnimplementedZITADELWebKeysServer
|
||||
webkey.UnimplementedWebKeyServiceServer
|
||||
command *command.Commands
|
||||
query *query.Queries
|
||||
}
|
||||
@@ -27,21 +27,21 @@ func CreateServer(
|
||||
}
|
||||
|
||||
func (s *Server) RegisterServer(grpcServer *grpc.Server) {
|
||||
webkey.RegisterZITADELWebKeysServer(grpcServer, s)
|
||||
webkey.RegisterWebKeyServiceServer(grpcServer, s)
|
||||
}
|
||||
|
||||
func (s *Server) AppName() string {
|
||||
return webkey.ZITADELWebKeys_ServiceDesc.ServiceName
|
||||
return webkey.WebKeyService_ServiceDesc.ServiceName
|
||||
}
|
||||
|
||||
func (s *Server) MethodPrefix() string {
|
||||
return webkey.ZITADELWebKeys_ServiceDesc.ServiceName
|
||||
return webkey.WebKeyService_ServiceDesc.ServiceName
|
||||
}
|
||||
|
||||
func (s *Server) AuthMethods() authz.MethodMapping {
|
||||
return webkey.ZITADELWebKeys_AuthMethods
|
||||
return webkey.WebKeyService_AuthMethods
|
||||
}
|
||||
|
||||
func (s *Server) RegisterGateway() server.RegisterGatewayFunc {
|
||||
return webkey.RegisterZITADELWebKeysHandler
|
||||
return webkey.RegisterWebKeyServiceHandler
|
||||
}
|
@@ -3,12 +3,12 @@ package webkey
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
resource_object "github.com/zitadel/zitadel/internal/api/grpc/resources/object/v3alpha"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
webkey "github.com/zitadel/zitadel/pkg/grpc/resources/webkey/v3alpha"
|
||||
webkey "github.com/zitadel/zitadel/pkg/grpc/webkey/v2beta"
|
||||
)
|
||||
|
||||
func (s *Server) CreateWebKey(ctx context.Context, req *webkey.CreateWebKeyRequest) (_ *webkey.CreateWebKeyResponse, err error) {
|
||||
@@ -24,7 +24,8 @@ func (s *Server) CreateWebKey(ctx context.Context, req *webkey.CreateWebKeyReque
|
||||
}
|
||||
|
||||
return &webkey.CreateWebKeyResponse{
|
||||
Details: resource_object.DomainToDetailsPb(webKey.ObjectDetails, object.OwnerType_OWNER_TYPE_INSTANCE, authz.GetInstance(ctx).InstanceID()),
|
||||
Id: webKey.KeyID,
|
||||
CreationDate: timestamppb.New(webKey.ObjectDetails.EventDate),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -41,7 +42,7 @@ func (s *Server) ActivateWebKey(ctx context.Context, req *webkey.ActivateWebKeyR
|
||||
}
|
||||
|
||||
return &webkey.ActivateWebKeyResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, authz.GetInstance(ctx).InstanceID()),
|
||||
ChangeDate: timestamppb.New(details.EventDate),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -52,13 +53,17 @@ func (s *Server) DeleteWebKey(ctx context.Context, req *webkey.DeleteWebKeyReque
|
||||
if err = checkWebKeyFeature(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.DeleteWebKey(ctx, req.GetId())
|
||||
deletedAt, err := s.command.DeleteWebKey(ctx, req.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var deletionDate *timestamppb.Timestamp
|
||||
if !deletedAt.IsZero() {
|
||||
deletionDate = timestamppb.New(deletedAt)
|
||||
}
|
||||
return &webkey.DeleteWebKeyResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_INSTANCE, authz.GetInstance(ctx).InstanceID()),
|
||||
DeletionDate: deletionDate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -75,7 +80,7 @@ func (s *Server) ListWebKeys(ctx context.Context, _ *webkey.ListWebKeysRequest)
|
||||
}
|
||||
|
||||
return &webkey.ListWebKeysResponse{
|
||||
WebKeys: webKeyDetailsListToPb(list, authz.GetInstance(ctx).InstanceID()),
|
||||
WebKeys: webKeyDetailsListToPb(list),
|
||||
}, nil
|
||||
}
|
||||
|
170
internal/api/grpc/webkey/v2beta/webkey_converter.go
Normal file
170
internal/api/grpc/webkey/v2beta/webkey_converter.go
Normal file
@@ -0,0 +1,170 @@
|
||||
package webkey
|
||||
|
||||
import (
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
webkey "github.com/zitadel/zitadel/pkg/grpc/webkey/v2beta"
|
||||
)
|
||||
|
||||
func createWebKeyRequestToConfig(req *webkey.CreateWebKeyRequest) crypto.WebKeyConfig {
|
||||
switch config := req.GetKey().(type) {
|
||||
case *webkey.CreateWebKeyRequest_Rsa:
|
||||
return rsaToCrypto(config.Rsa)
|
||||
case *webkey.CreateWebKeyRequest_Ecdsa:
|
||||
return ecdsaToCrypto(config.Ecdsa)
|
||||
case *webkey.CreateWebKeyRequest_Ed25519:
|
||||
return new(crypto.WebKeyED25519Config)
|
||||
default:
|
||||
return rsaToCrypto(nil)
|
||||
}
|
||||
}
|
||||
|
||||
func rsaToCrypto(config *webkey.RSA) *crypto.WebKeyRSAConfig {
|
||||
out := new(crypto.WebKeyRSAConfig)
|
||||
|
||||
switch config.GetBits() {
|
||||
case webkey.RSABits_RSA_BITS_UNSPECIFIED:
|
||||
out.Bits = crypto.RSABits2048
|
||||
case webkey.RSABits_RSA_BITS_2048:
|
||||
out.Bits = crypto.RSABits2048
|
||||
case webkey.RSABits_RSA_BITS_3072:
|
||||
out.Bits = crypto.RSABits3072
|
||||
case webkey.RSABits_RSA_BITS_4096:
|
||||
out.Bits = crypto.RSABits4096
|
||||
default:
|
||||
out.Bits = crypto.RSABits2048
|
||||
}
|
||||
|
||||
switch config.GetHasher() {
|
||||
case webkey.RSAHasher_RSA_HASHER_UNSPECIFIED:
|
||||
out.Hasher = crypto.RSAHasherSHA256
|
||||
case webkey.RSAHasher_RSA_HASHER_SHA256:
|
||||
out.Hasher = crypto.RSAHasherSHA256
|
||||
case webkey.RSAHasher_RSA_HASHER_SHA384:
|
||||
out.Hasher = crypto.RSAHasherSHA384
|
||||
case webkey.RSAHasher_RSA_HASHER_SHA512:
|
||||
out.Hasher = crypto.RSAHasherSHA512
|
||||
default:
|
||||
out.Hasher = crypto.RSAHasherSHA256
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func ecdsaToCrypto(config *webkey.ECDSA) *crypto.WebKeyECDSAConfig {
|
||||
out := new(crypto.WebKeyECDSAConfig)
|
||||
|
||||
switch config.GetCurve() {
|
||||
case webkey.ECDSACurve_ECDSA_CURVE_UNSPECIFIED:
|
||||
out.Curve = crypto.EllipticCurveP256
|
||||
case webkey.ECDSACurve_ECDSA_CURVE_P256:
|
||||
out.Curve = crypto.EllipticCurveP256
|
||||
case webkey.ECDSACurve_ECDSA_CURVE_P384:
|
||||
out.Curve = crypto.EllipticCurveP384
|
||||
case webkey.ECDSACurve_ECDSA_CURVE_P512:
|
||||
out.Curve = crypto.EllipticCurveP512
|
||||
default:
|
||||
out.Curve = crypto.EllipticCurveP256
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func webKeyDetailsListToPb(list []query.WebKeyDetails) []*webkey.WebKey {
|
||||
out := make([]*webkey.WebKey, len(list))
|
||||
for i := range list {
|
||||
out[i] = webKeyDetailsToPb(&list[i])
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func webKeyDetailsToPb(details *query.WebKeyDetails) *webkey.WebKey {
|
||||
out := &webkey.WebKey{
|
||||
Id: details.KeyID,
|
||||
CreationDate: timestamppb.New(details.CreationDate),
|
||||
ChangeDate: timestamppb.New(details.ChangeDate),
|
||||
State: webKeyStateToPb(details.State),
|
||||
}
|
||||
|
||||
switch config := details.Config.(type) {
|
||||
case *crypto.WebKeyRSAConfig:
|
||||
out.Key = &webkey.WebKey_Rsa{
|
||||
Rsa: webKeyRSAConfigToPb(config),
|
||||
}
|
||||
case *crypto.WebKeyECDSAConfig:
|
||||
out.Key = &webkey.WebKey_Ecdsa{
|
||||
Ecdsa: webKeyECDSAConfigToPb(config),
|
||||
}
|
||||
case *crypto.WebKeyED25519Config:
|
||||
out.Key = &webkey.WebKey_Ed25519{
|
||||
Ed25519: new(webkey.ED25519),
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func webKeyStateToPb(state domain.WebKeyState) webkey.State {
|
||||
switch state {
|
||||
case domain.WebKeyStateUnspecified:
|
||||
return webkey.State_STATE_UNSPECIFIED
|
||||
case domain.WebKeyStateInitial:
|
||||
return webkey.State_STATE_INITIAL
|
||||
case domain.WebKeyStateActive:
|
||||
return webkey.State_STATE_ACTIVE
|
||||
case domain.WebKeyStateInactive:
|
||||
return webkey.State_STATE_INACTIVE
|
||||
case domain.WebKeyStateRemoved:
|
||||
return webkey.State_STATE_REMOVED
|
||||
default:
|
||||
return webkey.State_STATE_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func webKeyRSAConfigToPb(config *crypto.WebKeyRSAConfig) *webkey.RSA {
|
||||
out := new(webkey.RSA)
|
||||
|
||||
switch config.Bits {
|
||||
case crypto.RSABitsUnspecified:
|
||||
out.Bits = webkey.RSABits_RSA_BITS_UNSPECIFIED
|
||||
case crypto.RSABits2048:
|
||||
out.Bits = webkey.RSABits_RSA_BITS_2048
|
||||
case crypto.RSABits3072:
|
||||
out.Bits = webkey.RSABits_RSA_BITS_3072
|
||||
case crypto.RSABits4096:
|
||||
out.Bits = webkey.RSABits_RSA_BITS_4096
|
||||
}
|
||||
|
||||
switch config.Hasher {
|
||||
case crypto.RSAHasherUnspecified:
|
||||
out.Hasher = webkey.RSAHasher_RSA_HASHER_UNSPECIFIED
|
||||
case crypto.RSAHasherSHA256:
|
||||
out.Hasher = webkey.RSAHasher_RSA_HASHER_SHA256
|
||||
case crypto.RSAHasherSHA384:
|
||||
out.Hasher = webkey.RSAHasher_RSA_HASHER_SHA384
|
||||
case crypto.RSAHasherSHA512:
|
||||
out.Hasher = webkey.RSAHasher_RSA_HASHER_SHA512
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
func webKeyECDSAConfigToPb(config *crypto.WebKeyECDSAConfig) *webkey.ECDSA {
|
||||
out := new(webkey.ECDSA)
|
||||
|
||||
switch config.Curve {
|
||||
case crypto.EllipticCurveUnspecified:
|
||||
out.Curve = webkey.ECDSACurve_ECDSA_CURVE_UNSPECIFIED
|
||||
case crypto.EllipticCurveP256:
|
||||
out.Curve = webkey.ECDSACurve_ECDSA_CURVE_P256
|
||||
case crypto.EllipticCurveP384:
|
||||
out.Curve = webkey.ECDSACurve_ECDSA_CURVE_P384
|
||||
case crypto.EllipticCurveP512:
|
||||
out.Curve = webkey.ECDSACurve_ECDSA_CURVE_P512
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
@@ -10,9 +10,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha"
|
||||
webkey "github.com/zitadel/zitadel/pkg/grpc/resources/webkey/v3alpha"
|
||||
webkey "github.com/zitadel/zitadel/pkg/grpc/webkey/v2beta"
|
||||
)
|
||||
|
||||
func Test_createWebKeyRequestToConfig(t *testing.T) {
|
||||
@@ -27,12 +25,10 @@ func Test_createWebKeyRequestToConfig(t *testing.T) {
|
||||
{
|
||||
name: "RSA",
|
||||
args: args{&webkey.CreateWebKeyRequest{
|
||||
Key: &webkey.WebKey{
|
||||
Config: &webkey.WebKey_Rsa{
|
||||
Rsa: &webkey.WebKeyRSAConfig{
|
||||
Bits: webkey.WebKeyRSAConfig_RSA_BITS_3072,
|
||||
Hasher: webkey.WebKeyRSAConfig_RSA_HASHER_SHA384,
|
||||
},
|
||||
Key: &webkey.CreateWebKeyRequest_Rsa{
|
||||
Rsa: &webkey.RSA{
|
||||
Bits: webkey.RSABits_RSA_BITS_3072,
|
||||
Hasher: webkey.RSAHasher_RSA_HASHER_SHA384,
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -44,11 +40,9 @@ func Test_createWebKeyRequestToConfig(t *testing.T) {
|
||||
{
|
||||
name: "ECDSA",
|
||||
args: args{&webkey.CreateWebKeyRequest{
|
||||
Key: &webkey.WebKey{
|
||||
Config: &webkey.WebKey_Ecdsa{
|
||||
Ecdsa: &webkey.WebKeyECDSAConfig{
|
||||
Curve: webkey.WebKeyECDSAConfig_ECDSA_CURVE_P384,
|
||||
},
|
||||
Key: &webkey.CreateWebKeyRequest_Ecdsa{
|
||||
Ecdsa: &webkey.ECDSA{
|
||||
Curve: webkey.ECDSACurve_ECDSA_CURVE_P384,
|
||||
},
|
||||
},
|
||||
}},
|
||||
@@ -59,10 +53,8 @@ func Test_createWebKeyRequestToConfig(t *testing.T) {
|
||||
{
|
||||
name: "ED25519",
|
||||
args: args{&webkey.CreateWebKeyRequest{
|
||||
Key: &webkey.WebKey{
|
||||
Config: &webkey.WebKey_Ed25519{
|
||||
Ed25519: &webkey.WebKeyED25519Config{},
|
||||
},
|
||||
Key: &webkey.CreateWebKeyRequest_Ed25519{
|
||||
Ed25519: &webkey.ED25519{},
|
||||
},
|
||||
}},
|
||||
want: &crypto.WebKeyED25519Config{},
|
||||
@@ -86,7 +78,7 @@ func Test_createWebKeyRequestToConfig(t *testing.T) {
|
||||
|
||||
func Test_webKeyRSAConfigToCrypto(t *testing.T) {
|
||||
type args struct {
|
||||
config *webkey.WebKeyRSAConfig
|
||||
config *webkey.RSA
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -95,9 +87,9 @@ func Test_webKeyRSAConfigToCrypto(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "unspecified",
|
||||
args: args{&webkey.WebKeyRSAConfig{
|
||||
Bits: webkey.WebKeyRSAConfig_RSA_BITS_UNSPECIFIED,
|
||||
Hasher: webkey.WebKeyRSAConfig_RSA_HASHER_UNSPECIFIED,
|
||||
args: args{&webkey.RSA{
|
||||
Bits: webkey.RSABits_RSA_BITS_UNSPECIFIED,
|
||||
Hasher: webkey.RSAHasher_RSA_HASHER_UNSPECIFIED,
|
||||
}},
|
||||
want: &crypto.WebKeyRSAConfig{
|
||||
Bits: crypto.RSABits2048,
|
||||
@@ -106,9 +98,9 @@ func Test_webKeyRSAConfigToCrypto(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "2048, RSA256",
|
||||
args: args{&webkey.WebKeyRSAConfig{
|
||||
Bits: webkey.WebKeyRSAConfig_RSA_BITS_2048,
|
||||
Hasher: webkey.WebKeyRSAConfig_RSA_HASHER_SHA256,
|
||||
args: args{&webkey.RSA{
|
||||
Bits: webkey.RSABits_RSA_BITS_2048,
|
||||
Hasher: webkey.RSAHasher_RSA_HASHER_SHA256,
|
||||
}},
|
||||
want: &crypto.WebKeyRSAConfig{
|
||||
Bits: crypto.RSABits2048,
|
||||
@@ -117,9 +109,9 @@ func Test_webKeyRSAConfigToCrypto(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "3072, RSA384",
|
||||
args: args{&webkey.WebKeyRSAConfig{
|
||||
Bits: webkey.WebKeyRSAConfig_RSA_BITS_3072,
|
||||
Hasher: webkey.WebKeyRSAConfig_RSA_HASHER_SHA384,
|
||||
args: args{&webkey.RSA{
|
||||
Bits: webkey.RSABits_RSA_BITS_3072,
|
||||
Hasher: webkey.RSAHasher_RSA_HASHER_SHA384,
|
||||
}},
|
||||
want: &crypto.WebKeyRSAConfig{
|
||||
Bits: crypto.RSABits3072,
|
||||
@@ -128,9 +120,9 @@ func Test_webKeyRSAConfigToCrypto(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "4096, RSA512",
|
||||
args: args{&webkey.WebKeyRSAConfig{
|
||||
Bits: webkey.WebKeyRSAConfig_RSA_BITS_4096,
|
||||
Hasher: webkey.WebKeyRSAConfig_RSA_HASHER_SHA512,
|
||||
args: args{&webkey.RSA{
|
||||
Bits: webkey.RSABits_RSA_BITS_4096,
|
||||
Hasher: webkey.RSAHasher_RSA_HASHER_SHA512,
|
||||
}},
|
||||
want: &crypto.WebKeyRSAConfig{
|
||||
Bits: crypto.RSABits4096,
|
||||
@@ -139,7 +131,7 @@ func Test_webKeyRSAConfigToCrypto(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "invalid",
|
||||
args: args{&webkey.WebKeyRSAConfig{
|
||||
args: args{&webkey.RSA{
|
||||
Bits: 99,
|
||||
Hasher: 99,
|
||||
}},
|
||||
@@ -151,7 +143,7 @@ func Test_webKeyRSAConfigToCrypto(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := webKeyRSAConfigToCrypto(tt.args.config)
|
||||
got := rsaToCrypto(tt.args.config)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
@@ -159,7 +151,7 @@ func Test_webKeyRSAConfigToCrypto(t *testing.T) {
|
||||
|
||||
func Test_webKeyECDSAConfigToCrypto(t *testing.T) {
|
||||
type args struct {
|
||||
config *webkey.WebKeyECDSAConfig
|
||||
config *webkey.ECDSA
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -168,8 +160,8 @@ func Test_webKeyECDSAConfigToCrypto(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "unspecified",
|
||||
args: args{&webkey.WebKeyECDSAConfig{
|
||||
Curve: webkey.WebKeyECDSAConfig_ECDSA_CURVE_UNSPECIFIED,
|
||||
args: args{&webkey.ECDSA{
|
||||
Curve: webkey.ECDSACurve_ECDSA_CURVE_UNSPECIFIED,
|
||||
}},
|
||||
want: &crypto.WebKeyECDSAConfig{
|
||||
Curve: crypto.EllipticCurveP256,
|
||||
@@ -177,8 +169,8 @@ func Test_webKeyECDSAConfigToCrypto(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "P256",
|
||||
args: args{&webkey.WebKeyECDSAConfig{
|
||||
Curve: webkey.WebKeyECDSAConfig_ECDSA_CURVE_P256,
|
||||
args: args{&webkey.ECDSA{
|
||||
Curve: webkey.ECDSACurve_ECDSA_CURVE_P256,
|
||||
}},
|
||||
want: &crypto.WebKeyECDSAConfig{
|
||||
Curve: crypto.EllipticCurveP256,
|
||||
@@ -186,8 +178,8 @@ func Test_webKeyECDSAConfigToCrypto(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "P384",
|
||||
args: args{&webkey.WebKeyECDSAConfig{
|
||||
Curve: webkey.WebKeyECDSAConfig_ECDSA_CURVE_P384,
|
||||
args: args{&webkey.ECDSA{
|
||||
Curve: webkey.ECDSACurve_ECDSA_CURVE_P384,
|
||||
}},
|
||||
want: &crypto.WebKeyECDSAConfig{
|
||||
Curve: crypto.EllipticCurveP384,
|
||||
@@ -195,8 +187,8 @@ func Test_webKeyECDSAConfigToCrypto(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "P512",
|
||||
args: args{&webkey.WebKeyECDSAConfig{
|
||||
Curve: webkey.WebKeyECDSAConfig_ECDSA_CURVE_P512,
|
||||
args: args{&webkey.ECDSA{
|
||||
Curve: webkey.ECDSACurve_ECDSA_CURVE_P512,
|
||||
}},
|
||||
want: &crypto.WebKeyECDSAConfig{
|
||||
Curve: crypto.EllipticCurveP512,
|
||||
@@ -204,7 +196,7 @@ func Test_webKeyECDSAConfigToCrypto(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "invalid",
|
||||
args: args{&webkey.WebKeyECDSAConfig{
|
||||
args: args{&webkey.ECDSA{
|
||||
Curve: 99,
|
||||
}},
|
||||
want: &crypto.WebKeyECDSAConfig{
|
||||
@@ -214,14 +206,13 @@ func Test_webKeyECDSAConfigToCrypto(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := webKeyECDSAConfigToCrypto(tt.args.config)
|
||||
got := ecdsaToCrypto(tt.args.config)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_webKeyDetailsListToPb(t *testing.T) {
|
||||
instanceID := "ownerid"
|
||||
list := []query.WebKeyDetails{
|
||||
{
|
||||
KeyID: "key1",
|
||||
@@ -243,52 +234,41 @@ func Test_webKeyDetailsListToPb(t *testing.T) {
|
||||
Config: &crypto.WebKeyED25519Config{},
|
||||
},
|
||||
}
|
||||
want := []*webkey.GetWebKey{
|
||||
want := []*webkey.WebKey{
|
||||
{
|
||||
Details: &resource_object.Details{
|
||||
Id: "key1",
|
||||
Created: ×tamppb.Timestamp{Seconds: 123, Nanos: 456},
|
||||
Changed: ×tamppb.Timestamp{Seconds: 789, Nanos: 0},
|
||||
Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID},
|
||||
},
|
||||
State: webkey.WebKeyState_STATE_ACTIVE,
|
||||
Config: &webkey.WebKey{
|
||||
Config: &webkey.WebKey_Rsa{
|
||||
Rsa: &webkey.WebKeyRSAConfig{
|
||||
Bits: webkey.WebKeyRSAConfig_RSA_BITS_3072,
|
||||
Hasher: webkey.WebKeyRSAConfig_RSA_HASHER_SHA384,
|
||||
},
|
||||
Id: "key1",
|
||||
CreationDate: ×tamppb.Timestamp{Seconds: 123, Nanos: 456},
|
||||
ChangeDate: ×tamppb.Timestamp{Seconds: 789, Nanos: 0},
|
||||
State: webkey.State_STATE_ACTIVE,
|
||||
Key: &webkey.WebKey_Rsa{
|
||||
Rsa: &webkey.RSA{
|
||||
Bits: webkey.RSABits_RSA_BITS_3072,
|
||||
Hasher: webkey.RSAHasher_RSA_HASHER_SHA384,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Details: &resource_object.Details{
|
||||
Id: "key2",
|
||||
Created: ×tamppb.Timestamp{Seconds: 123, Nanos: 456},
|
||||
Changed: ×tamppb.Timestamp{Seconds: 789, Nanos: 0},
|
||||
Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID},
|
||||
},
|
||||
State: webkey.WebKeyState_STATE_ACTIVE,
|
||||
Config: &webkey.WebKey{
|
||||
Config: &webkey.WebKey_Ed25519{
|
||||
Ed25519: &webkey.WebKeyED25519Config{},
|
||||
},
|
||||
Id: "key2",
|
||||
CreationDate: ×tamppb.Timestamp{Seconds: 123, Nanos: 456},
|
||||
ChangeDate: ×tamppb.Timestamp{Seconds: 789, Nanos: 0},
|
||||
State: webkey.State_STATE_ACTIVE,
|
||||
Key: &webkey.WebKey_Ed25519{
|
||||
Ed25519: &webkey.ED25519{},
|
||||
},
|
||||
},
|
||||
}
|
||||
got := webKeyDetailsListToPb(list, instanceID)
|
||||
got := webKeyDetailsListToPb(list)
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
func Test_webKeyDetailsToPb(t *testing.T) {
|
||||
instanceID := "ownerid"
|
||||
type args struct {
|
||||
details *query.WebKeyDetails
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *webkey.GetWebKey
|
||||
want *webkey.WebKey
|
||||
}{
|
||||
{
|
||||
name: "RSA",
|
||||
@@ -303,20 +283,15 @@ func Test_webKeyDetailsToPb(t *testing.T) {
|
||||
Hasher: crypto.RSAHasherSHA384,
|
||||
},
|
||||
}},
|
||||
want: &webkey.GetWebKey{
|
||||
Details: &resource_object.Details{
|
||||
Id: "keyID",
|
||||
Created: ×tamppb.Timestamp{Seconds: 123, Nanos: 456},
|
||||
Changed: ×tamppb.Timestamp{Seconds: 789, Nanos: 0},
|
||||
Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID},
|
||||
},
|
||||
State: webkey.WebKeyState_STATE_ACTIVE,
|
||||
Config: &webkey.WebKey{
|
||||
Config: &webkey.WebKey_Rsa{
|
||||
Rsa: &webkey.WebKeyRSAConfig{
|
||||
Bits: webkey.WebKeyRSAConfig_RSA_BITS_3072,
|
||||
Hasher: webkey.WebKeyRSAConfig_RSA_HASHER_SHA384,
|
||||
},
|
||||
want: &webkey.WebKey{
|
||||
Id: "keyID",
|
||||
CreationDate: ×tamppb.Timestamp{Seconds: 123, Nanos: 456},
|
||||
ChangeDate: ×tamppb.Timestamp{Seconds: 789, Nanos: 0},
|
||||
State: webkey.State_STATE_ACTIVE,
|
||||
Key: &webkey.WebKey_Rsa{
|
||||
Rsa: &webkey.RSA{
|
||||
Bits: webkey.RSABits_RSA_BITS_3072,
|
||||
Hasher: webkey.RSAHasher_RSA_HASHER_SHA384,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -333,19 +308,14 @@ func Test_webKeyDetailsToPb(t *testing.T) {
|
||||
Curve: crypto.EllipticCurveP384,
|
||||
},
|
||||
}},
|
||||
want: &webkey.GetWebKey{
|
||||
Details: &resource_object.Details{
|
||||
Id: "keyID",
|
||||
Created: ×tamppb.Timestamp{Seconds: 123, Nanos: 456},
|
||||
Changed: ×tamppb.Timestamp{Seconds: 789, Nanos: 0},
|
||||
Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID},
|
||||
},
|
||||
State: webkey.WebKeyState_STATE_ACTIVE,
|
||||
Config: &webkey.WebKey{
|
||||
Config: &webkey.WebKey_Ecdsa{
|
||||
Ecdsa: &webkey.WebKeyECDSAConfig{
|
||||
Curve: webkey.WebKeyECDSAConfig_ECDSA_CURVE_P384,
|
||||
},
|
||||
want: &webkey.WebKey{
|
||||
Id: "keyID",
|
||||
CreationDate: ×tamppb.Timestamp{Seconds: 123, Nanos: 456},
|
||||
ChangeDate: ×tamppb.Timestamp{Seconds: 789, Nanos: 0},
|
||||
State: webkey.State_STATE_ACTIVE,
|
||||
Key: &webkey.WebKey_Ecdsa{
|
||||
Ecdsa: &webkey.ECDSA{
|
||||
Curve: webkey.ECDSACurve_ECDSA_CURVE_P384,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -360,25 +330,20 @@ func Test_webKeyDetailsToPb(t *testing.T) {
|
||||
State: domain.WebKeyStateActive,
|
||||
Config: &crypto.WebKeyED25519Config{},
|
||||
}},
|
||||
want: &webkey.GetWebKey{
|
||||
Details: &resource_object.Details{
|
||||
Id: "keyID",
|
||||
Created: ×tamppb.Timestamp{Seconds: 123, Nanos: 456},
|
||||
Changed: ×tamppb.Timestamp{Seconds: 789, Nanos: 0},
|
||||
Owner: &object.Owner{Type: object.OwnerType_OWNER_TYPE_INSTANCE, Id: instanceID},
|
||||
},
|
||||
State: webkey.WebKeyState_STATE_ACTIVE,
|
||||
Config: &webkey.WebKey{
|
||||
Config: &webkey.WebKey_Ed25519{
|
||||
Ed25519: &webkey.WebKeyED25519Config{},
|
||||
},
|
||||
want: &webkey.WebKey{
|
||||
Id: "keyID",
|
||||
CreationDate: ×tamppb.Timestamp{Seconds: 123, Nanos: 456},
|
||||
ChangeDate: ×tamppb.Timestamp{Seconds: 789, Nanos: 0},
|
||||
State: webkey.State_STATE_ACTIVE,
|
||||
Key: &webkey.WebKey_Ed25519{
|
||||
Ed25519: &webkey.ED25519{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := webKeyDetailsToPb(tt.args.details, instanceID)
|
||||
got := webKeyDetailsToPb(tt.args.details)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
@@ -391,37 +356,37 @@ func Test_webKeyStateToPb(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want webkey.WebKeyState
|
||||
want webkey.State
|
||||
}{
|
||||
{
|
||||
name: "unspecified",
|
||||
args: args{domain.WebKeyStateUnspecified},
|
||||
want: webkey.WebKeyState_STATE_UNSPECIFIED,
|
||||
want: webkey.State_STATE_UNSPECIFIED,
|
||||
},
|
||||
{
|
||||
name: "initial",
|
||||
args: args{domain.WebKeyStateInitial},
|
||||
want: webkey.WebKeyState_STATE_INITIAL,
|
||||
want: webkey.State_STATE_INITIAL,
|
||||
},
|
||||
{
|
||||
name: "active",
|
||||
args: args{domain.WebKeyStateActive},
|
||||
want: webkey.WebKeyState_STATE_ACTIVE,
|
||||
want: webkey.State_STATE_ACTIVE,
|
||||
},
|
||||
{
|
||||
name: "inactive",
|
||||
args: args{domain.WebKeyStateInactive},
|
||||
want: webkey.WebKeyState_STATE_INACTIVE,
|
||||
want: webkey.State_STATE_INACTIVE,
|
||||
},
|
||||
{
|
||||
name: "removed",
|
||||
args: args{domain.WebKeyStateRemoved},
|
||||
want: webkey.WebKeyState_STATE_REMOVED,
|
||||
want: webkey.State_STATE_REMOVED,
|
||||
},
|
||||
{
|
||||
name: "invalid",
|
||||
args: args{99},
|
||||
want: webkey.WebKeyState_STATE_UNSPECIFIED,
|
||||
want: webkey.State_STATE_UNSPECIFIED,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
@@ -439,7 +404,7 @@ func Test_webKeyRSAConfigToPb(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *webkey.WebKeyRSAConfig
|
||||
want *webkey.RSA
|
||||
}{
|
||||
{
|
||||
name: "2048, RSA256",
|
||||
@@ -447,9 +412,9 @@ func Test_webKeyRSAConfigToPb(t *testing.T) {
|
||||
Bits: crypto.RSABits2048,
|
||||
Hasher: crypto.RSAHasherSHA256,
|
||||
}},
|
||||
want: &webkey.WebKeyRSAConfig{
|
||||
Bits: webkey.WebKeyRSAConfig_RSA_BITS_2048,
|
||||
Hasher: webkey.WebKeyRSAConfig_RSA_HASHER_SHA256,
|
||||
want: &webkey.RSA{
|
||||
Bits: webkey.RSABits_RSA_BITS_2048,
|
||||
Hasher: webkey.RSAHasher_RSA_HASHER_SHA256,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -458,9 +423,9 @@ func Test_webKeyRSAConfigToPb(t *testing.T) {
|
||||
Bits: crypto.RSABits3072,
|
||||
Hasher: crypto.RSAHasherSHA384,
|
||||
}},
|
||||
want: &webkey.WebKeyRSAConfig{
|
||||
Bits: webkey.WebKeyRSAConfig_RSA_BITS_3072,
|
||||
Hasher: webkey.WebKeyRSAConfig_RSA_HASHER_SHA384,
|
||||
want: &webkey.RSA{
|
||||
Bits: webkey.RSABits_RSA_BITS_3072,
|
||||
Hasher: webkey.RSAHasher_RSA_HASHER_SHA384,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -469,9 +434,9 @@ func Test_webKeyRSAConfigToPb(t *testing.T) {
|
||||
Bits: crypto.RSABits4096,
|
||||
Hasher: crypto.RSAHasherSHA512,
|
||||
}},
|
||||
want: &webkey.WebKeyRSAConfig{
|
||||
Bits: webkey.WebKeyRSAConfig_RSA_BITS_4096,
|
||||
Hasher: webkey.WebKeyRSAConfig_RSA_HASHER_SHA512,
|
||||
want: &webkey.RSA{
|
||||
Bits: webkey.RSABits_RSA_BITS_4096,
|
||||
Hasher: webkey.RSAHasher_RSA_HASHER_SHA512,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -490,15 +455,15 @@ func Test_webKeyECDSAConfigToPb(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *webkey.WebKeyECDSAConfig
|
||||
want *webkey.ECDSA
|
||||
}{
|
||||
{
|
||||
name: "P256",
|
||||
args: args{&crypto.WebKeyECDSAConfig{
|
||||
Curve: crypto.EllipticCurveP256,
|
||||
}},
|
||||
want: &webkey.WebKeyECDSAConfig{
|
||||
Curve: webkey.WebKeyECDSAConfig_ECDSA_CURVE_P256,
|
||||
want: &webkey.ECDSA{
|
||||
Curve: webkey.ECDSACurve_ECDSA_CURVE_P256,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -506,8 +471,8 @@ func Test_webKeyECDSAConfigToPb(t *testing.T) {
|
||||
args: args{&crypto.WebKeyECDSAConfig{
|
||||
Curve: crypto.EllipticCurveP384,
|
||||
}},
|
||||
want: &webkey.WebKeyECDSAConfig{
|
||||
Curve: webkey.WebKeyECDSAConfig_ECDSA_CURVE_P384,
|
||||
want: &webkey.ECDSA{
|
||||
Curve: webkey.ECDSACurve_ECDSA_CURVE_P384,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -515,8 +480,8 @@ func Test_webKeyECDSAConfigToPb(t *testing.T) {
|
||||
args: args{&crypto.WebKeyECDSAConfig{
|
||||
Curve: crypto.EllipticCurveP512,
|
||||
}},
|
||||
want: &webkey.WebKeyECDSAConfig{
|
||||
Curve: webkey.WebKeyECDSAConfig_ECDSA_CURVE_P512,
|
||||
want: &webkey.ECDSA{
|
||||
Curve: webkey.ECDSACurve_ECDSA_CURVE_P512,
|
||||
},
|
||||
},
|
||||
}
|
@@ -14,14 +14,16 @@ import (
|
||||
)
|
||||
|
||||
type AuthInterceptor struct {
|
||||
verifier authz.APITokenVerifier
|
||||
authConfig authz.Config
|
||||
verifier authz.APITokenVerifier
|
||||
authConfig authz.Config
|
||||
systemAuthConfig authz.Config
|
||||
}
|
||||
|
||||
func AuthorizationInterceptor(verifier authz.APITokenVerifier, authConfig authz.Config) *AuthInterceptor {
|
||||
func AuthorizationInterceptor(verifier authz.APITokenVerifier, systemAuthConfig authz.Config, authConfig authz.Config) *AuthInterceptor {
|
||||
return &AuthInterceptor{
|
||||
verifier: verifier,
|
||||
authConfig: authConfig,
|
||||
verifier: verifier,
|
||||
authConfig: authConfig,
|
||||
systemAuthConfig: systemAuthConfig,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +33,7 @@ func (a *AuthInterceptor) Handler(next http.Handler) http.Handler {
|
||||
|
||||
func (a *AuthInterceptor) HandlerFunc(next http.Handler) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, err := authorize(r, a.verifier, a.authConfig)
|
||||
ctx, err := authorize(r, a.verifier, a.systemAuthConfig, a.authConfig)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusUnauthorized)
|
||||
return
|
||||
@@ -44,7 +46,7 @@ func (a *AuthInterceptor) HandlerFunc(next http.Handler) http.HandlerFunc {
|
||||
|
||||
func (a *AuthInterceptor) HandlerFuncWithError(next HandlerFuncWithError) HandlerFuncWithError {
|
||||
return func(w http.ResponseWriter, r *http.Request) error {
|
||||
ctx, err := authorize(r, a.verifier, a.authConfig)
|
||||
ctx, err := authorize(r, a.verifier, a.systemAuthConfig, a.authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -56,7 +58,7 @@ func (a *AuthInterceptor) HandlerFuncWithError(next HandlerFuncWithError) Handle
|
||||
|
||||
type httpReq struct{}
|
||||
|
||||
func authorize(r *http.Request, verifier authz.APITokenVerifier, authConfig authz.Config) (_ context.Context, err error) {
|
||||
func authorize(r *http.Request, verifier authz.APITokenVerifier, systemAuthConfig authz.Config, authConfig authz.Config) (_ context.Context, err error) {
|
||||
ctx := r.Context()
|
||||
|
||||
authOpt, needsToken := checkAuthMethod(r, verifier)
|
||||
@@ -71,7 +73,7 @@ func authorize(r *http.Request, verifier authz.APITokenVerifier, authConfig auth
|
||||
return nil, zerrors.ThrowUnauthenticated(nil, "AUT-1179", "auth header missing")
|
||||
}
|
||||
|
||||
ctxSetter, err := authz.CheckUserAuthorization(authCtx, &httpReq{}, authToken, http_util.GetOrgID(r), "", verifier, authConfig, authOpt, r.RequestURI)
|
||||
ctxSetter, err := authz.CheckUserAuthorization(authCtx, &httpReq{}, authToken, http_util.GetOrgID(r), "", verifier, systemAuthConfig.RolePermissionMappings, authConfig.RolePermissionMappings, authOpt, r.RequestURI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
@@ -14,5 +15,5 @@ var (
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
i18n.SupportLanguages(SupportedLanguages...)
|
||||
m.Run()
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
@@ -417,7 +417,6 @@ func (o *OPStorage) getMaxKeySequence(ctx context.Context) (float64, error) {
|
||||
eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxSequence).
|
||||
ResourceOwner(authz.GetInstance(ctx).InstanceID()).
|
||||
AwaitOpenTransactions().
|
||||
AllowTimeTravel().
|
||||
AddQuery().
|
||||
AggregateTypes(
|
||||
keypair.AggregateType,
|
||||
|
@@ -5,444 +5,438 @@ package integration_test
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/scim/resources"
|
||||
"github.com/zitadel/zitadel/internal/api/scim/schemas"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/internal/integration/scim"
|
||||
"github.com/zitadel/zitadel/internal/test"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||
user_v2 "github.com/zitadel/zitadel/pkg/grpc/user/v2"
|
||||
)
|
||||
|
||||
var totalCountOfHumanUsers = 13
|
||||
|
||||
func TestListUser(t *testing.T) {
|
||||
createdUserIDs := createUsers(t, CTX, Instance.DefaultOrg.Id)
|
||||
defer func() {
|
||||
// only the full user needs to be deleted, all others have random identification data
|
||||
// fullUser is always the first one.
|
||||
_, err := Instance.Client.UserV2.DeleteUser(CTX, &user_v2.DeleteUserRequest{
|
||||
UserId: createdUserIDs[0],
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
/*
|
||||
func TestListUser(t *testing.T) {
|
||||
createdUserIDs := createUsers(t, CTX, Instance.DefaultOrg.Id)
|
||||
defer func() {
|
||||
// only the full user needs to be deleted, all others have random identification data
|
||||
// fullUser is always the first one.
|
||||
_, err := Instance.Client.UserV2.DeleteUser(CTX, &user_v2.DeleteUserRequest{
|
||||
UserId: createdUserIDs[0],
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}()
|
||||
|
||||
// secondary organization with same set of users,
|
||||
// these should never be modified.
|
||||
// This allows testing list requests without filters.
|
||||
iamOwnerCtx := Instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
secondaryOrg := Instance.CreateOrganization(iamOwnerCtx, gofakeit.Name(), gofakeit.Email())
|
||||
secondaryOrgCreatedUserIDs := createUsers(t, iamOwnerCtx, secondaryOrg.OrganizationId)
|
||||
// secondary organization with same set of users,
|
||||
// these should never be modified.
|
||||
// This allows testing list requests without filters.
|
||||
iamOwnerCtx := Instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
secondaryOrg := Instance.CreateOrganization(iamOwnerCtx, gofakeit.Name(), gofakeit.Email())
|
||||
secondaryOrgCreatedUserIDs := createUsers(t, iamOwnerCtx, secondaryOrg.OrganizationId)
|
||||
|
||||
testsInitializedUtc := time.Now().UTC()
|
||||
testsInitializedUtc := time.Now().UTC()
|
||||
|
||||
// Wait one second to ensure a change in the least significant value of the timestamp.
|
||||
time.Sleep(time.Second)
|
||||
// Wait one second to ensure a change in the least significant value of the timestamp.
|
||||
time.Sleep(time.Second)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
orgID string
|
||||
req *scim.ListRequest
|
||||
prepare func(require.TestingT) *scim.ListRequest
|
||||
wantErr bool
|
||||
errorStatus int
|
||||
errorType string
|
||||
assert func(assert.TestingT, *scim.ListResponse[*resources.ScimUser])
|
||||
cleanup func(require.TestingT)
|
||||
}{
|
||||
{
|
||||
name: "not authenticated",
|
||||
ctx: context.Background(),
|
||||
req: new(scim.ListRequest),
|
||||
wantErr: true,
|
||||
errorStatus: http.StatusUnauthorized,
|
||||
},
|
||||
{
|
||||
name: "no permissions",
|
||||
ctx: Instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||
req: new(scim.ListRequest),
|
||||
wantErr: true,
|
||||
errorStatus: http.StatusNotFound,
|
||||
},
|
||||
{
|
||||
name: "unknown sort order",
|
||||
req: &scim.ListRequest{
|
||||
SortBy: gu.Ptr("id"),
|
||||
SortOrder: gu.Ptr(scim.ListRequestSortOrder("fooBar")),
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
orgID string
|
||||
req *scim.ListRequest
|
||||
prepare func(require.TestingT) *scim.ListRequest
|
||||
wantErr bool
|
||||
errorStatus int
|
||||
errorType string
|
||||
assert func(assert.TestingT, *scim.ListResponse[*resources.ScimUser])
|
||||
cleanup func(require.TestingT)
|
||||
}{
|
||||
{
|
||||
name: "not authenticated",
|
||||
ctx: context.Background(),
|
||||
req: new(scim.ListRequest),
|
||||
wantErr: true,
|
||||
errorStatus: http.StatusUnauthorized,
|
||||
},
|
||||
wantErr: true,
|
||||
errorType: "invalidValue",
|
||||
},
|
||||
{
|
||||
name: "unknown sort field",
|
||||
req: &scim.ListRequest{
|
||||
SortBy: gu.Ptr("fooBar"),
|
||||
{
|
||||
name: "no permissions",
|
||||
ctx: Instance.WithAuthorization(CTX, integration.UserTypeNoPermission),
|
||||
req: new(scim.ListRequest),
|
||||
wantErr: true,
|
||||
errorStatus: http.StatusNotFound,
|
||||
},
|
||||
wantErr: true,
|
||||
errorType: "invalidValue",
|
||||
},
|
||||
{
|
||||
name: "custom sort field",
|
||||
req: &scim.ListRequest{
|
||||
SortBy: gu.Ptr("externalid"),
|
||||
{
|
||||
name: "unknown sort order",
|
||||
req: &scim.ListRequest{
|
||||
SortBy: gu.Ptr("id"),
|
||||
SortOrder: gu.Ptr(scim.ListRequestSortOrder("fooBar")),
|
||||
},
|
||||
wantErr: true,
|
||||
errorType: "invalidValue",
|
||||
},
|
||||
wantErr: true,
|
||||
errorType: "invalidValue",
|
||||
},
|
||||
{
|
||||
name: "unknown filter field",
|
||||
req: &scim.ListRequest{
|
||||
Filter: gu.Ptr(`fooBar eq "10"`),
|
||||
{
|
||||
name: "unknown sort field",
|
||||
req: &scim.ListRequest{
|
||||
SortBy: gu.Ptr("fooBar"),
|
||||
},
|
||||
wantErr: true,
|
||||
errorType: "invalidValue",
|
||||
},
|
||||
wantErr: true,
|
||||
errorType: "invalidFilter",
|
||||
},
|
||||
{
|
||||
name: "invalid filter",
|
||||
req: &scim.ListRequest{
|
||||
Filter: gu.Ptr(`fooBarBaz`),
|
||||
{
|
||||
name: "custom sort field",
|
||||
req: &scim.ListRequest{
|
||||
SortBy: gu.Ptr("externalid"),
|
||||
},
|
||||
wantErr: true,
|
||||
errorType: "invalidValue",
|
||||
},
|
||||
wantErr: true,
|
||||
errorType: "invalidFilter",
|
||||
},
|
||||
{
|
||||
name: "list users without filter",
|
||||
// use other org, modifications of users happens only on primary org
|
||||
orgID: secondaryOrg.OrganizationId,
|
||||
ctx: iamOwnerCtx,
|
||||
req: new(scim.ListRequest),
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Equal(t, 100, resp.ItemsPerPage)
|
||||
assert.Equal(t, totalCountOfHumanUsers, resp.TotalResults)
|
||||
assert.Equal(t, 1, resp.StartIndex)
|
||||
assert.Len(t, resp.Resources, totalCountOfHumanUsers)
|
||||
{
|
||||
name: "unknown filter field",
|
||||
req: &scim.ListRequest{
|
||||
Filter: gu.Ptr(`fooBar eq "10"`),
|
||||
},
|
||||
wantErr: true,
|
||||
errorType: "invalidFilter",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list paged sorted users without filter",
|
||||
// use other org, modifications of users happens only on primary org
|
||||
orgID: secondaryOrg.OrganizationId,
|
||||
ctx: iamOwnerCtx,
|
||||
req: &scim.ListRequest{
|
||||
Count: gu.Ptr(2),
|
||||
StartIndex: gu.Ptr(5),
|
||||
SortOrder: gu.Ptr(scim.ListRequestSortOrderAsc),
|
||||
SortBy: gu.Ptr("username"),
|
||||
{
|
||||
name: "invalid filter",
|
||||
req: &scim.ListRequest{
|
||||
Filter: gu.Ptr(`fooBarBaz`),
|
||||
},
|
||||
wantErr: true,
|
||||
errorType: "invalidFilter",
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
// sort the created users with usernames instead of creation date
|
||||
sortedResources := sortScimUserByUsername(resp.Resources)
|
||||
{
|
||||
name: "list users without filter",
|
||||
// use other org, modifications of users happens only on primary org
|
||||
orgID: secondaryOrg.OrganizationId,
|
||||
ctx: iamOwnerCtx,
|
||||
req: new(scim.ListRequest),
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Equal(t, 100, resp.ItemsPerPage)
|
||||
assert.Equal(t, totalCountOfHumanUsers, resp.TotalResults)
|
||||
assert.Equal(t, 1, resp.StartIndex)
|
||||
assert.Len(t, resp.Resources, totalCountOfHumanUsers)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list paged sorted users without filter",
|
||||
// use other org, modifications of users happens only on primary org
|
||||
orgID: secondaryOrg.OrganizationId,
|
||||
ctx: iamOwnerCtx,
|
||||
req: &scim.ListRequest{
|
||||
Count: gu.Ptr(2),
|
||||
StartIndex: gu.Ptr(5),
|
||||
SortOrder: gu.Ptr(scim.ListRequestSortOrderAsc),
|
||||
SortBy: gu.Ptr("username"),
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
// sort the created users with usernames instead of creation date
|
||||
sortedResources := sortScimUserByUsername(resp.Resources)
|
||||
|
||||
assert.Equal(t, 2, resp.ItemsPerPage)
|
||||
assert.Equal(t, totalCountOfHumanUsers, resp.TotalResults)
|
||||
assert.Equal(t, 5, resp.StartIndex)
|
||||
assert.Len(t, sortedResources, 2)
|
||||
assert.True(t, strings.HasPrefix(sortedResources[0].UserName, "scim-username-1: "))
|
||||
assert.True(t, strings.HasPrefix(sortedResources[1].UserName, "scim-username-2: "))
|
||||
assert.Equal(t, 2, resp.ItemsPerPage)
|
||||
assert.Equal(t, totalCountOfHumanUsers, resp.TotalResults)
|
||||
assert.Equal(t, 5, resp.StartIndex)
|
||||
assert.Len(t, sortedResources, 2)
|
||||
assert.True(t, strings.HasPrefix(sortedResources[0].UserName, "scim-username-1: "), "got %q", resp.Resources[0].UserName)
|
||||
assert.True(t, strings.HasPrefix(sortedResources[1].UserName, "scim-username-2: "), "got %q", resp.Resources[1].UserName)
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list users with simple filter",
|
||||
req: &scim.ListRequest{
|
||||
Filter: gu.Ptr(`username sw "scim-username-1"`),
|
||||
{
|
||||
name: "list users with simple filter",
|
||||
req: &scim.ListRequest{
|
||||
Filter: gu.Ptr(`username sw "scim-username-1"`),
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Equal(t, 100, resp.ItemsPerPage)
|
||||
assert.Equal(t, 2, resp.TotalResults)
|
||||
assert.Equal(t, 1, resp.StartIndex)
|
||||
assert.Len(t, resp.Resources, 2)
|
||||
for _, resource := range resp.Resources {
|
||||
assert.True(t, strings.HasPrefix(resource.UserName, "scim-username-1"))
|
||||
}
|
||||
},
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Equal(t, 100, resp.ItemsPerPage)
|
||||
assert.Equal(t, 2, resp.TotalResults)
|
||||
assert.Equal(t, 1, resp.StartIndex)
|
||||
assert.Len(t, resp.Resources, 2)
|
||||
for _, resource := range resp.Resources {
|
||||
assert.True(t, strings.HasPrefix(resource.UserName, "scim-username-1"))
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list paged sorted users with filter",
|
||||
req: &scim.ListRequest{
|
||||
Count: gu.Ptr(5),
|
||||
StartIndex: gu.Ptr(1),
|
||||
SortOrder: gu.Ptr(scim.ListRequestSortOrderAsc),
|
||||
SortBy: gu.Ptr("username"),
|
||||
Filter: gu.Ptr(`emails sw "scim-email-1" and emails ew "@example.com"`),
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
// sort the created users with usernames instead of creation date
|
||||
sortedResources := sortScimUserByUsername(resp.Resources)
|
||||
{
|
||||
name: "list paged sorted users with filter",
|
||||
req: &scim.ListRequest{
|
||||
Count: gu.Ptr(5),
|
||||
StartIndex: gu.Ptr(1),
|
||||
SortOrder: gu.Ptr(scim.ListRequestSortOrderAsc),
|
||||
SortBy: gu.Ptr("username"),
|
||||
Filter: gu.Ptr(`emails sw "scim-email-1" and emails ew "@example.com"`),
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
// sort the created users with usernames instead of creation date
|
||||
sortedResources := sortScimUserByUsername(resp.Resources)
|
||||
|
||||
assert.Equal(t, 5, resp.ItemsPerPage)
|
||||
assert.Equal(t, 2, resp.TotalResults)
|
||||
assert.Equal(t, 1, resp.StartIndex)
|
||||
assert.Len(t, sortedResources, 2)
|
||||
for _, resource := range sortedResources {
|
||||
assert.True(t, strings.HasPrefix(resource.UserName, "scim-username-1"))
|
||||
assert.Len(t, resource.Emails, 1)
|
||||
assert.True(t, strings.HasPrefix(resource.Emails[0].Value, "scim-email-1"))
|
||||
assert.True(t, strings.HasSuffix(resource.Emails[0].Value, "@example.com"))
|
||||
}
|
||||
assert.Equal(t, 5, resp.ItemsPerPage)
|
||||
assert.Equal(t, 2, resp.TotalResults)
|
||||
assert.Equal(t, 1, resp.StartIndex)
|
||||
assert.Len(t, sortedResources, 2)
|
||||
for _, resource := range sortedResources {
|
||||
assert.True(t, strings.HasPrefix(resource.UserName, "scim-username-1"))
|
||||
assert.Len(t, resource.Emails, 1)
|
||||
assert.True(t, strings.HasPrefix(resource.Emails[0].Value, "scim-email-1"))
|
||||
assert.True(t, strings.HasSuffix(resource.Emails[0].Value, "@example.com"))
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list paged sorted users with filter as post",
|
||||
req: &scim.ListRequest{
|
||||
Schemas: []schemas.ScimSchemaType{schemas.IdSearchRequest},
|
||||
Count: gu.Ptr(5),
|
||||
StartIndex: gu.Ptr(1),
|
||||
SortOrder: gu.Ptr(scim.ListRequestSortOrderAsc),
|
||||
SortBy: gu.Ptr("username"),
|
||||
Filter: gu.Ptr(`emails sw "scim-email-1" and emails ew "@example.com"`),
|
||||
SendAsPost: true,
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
// sort the created users with usernames instead of creation date
|
||||
sortedResources := sortScimUserByUsername(resp.Resources)
|
||||
{
|
||||
name: "list paged sorted users with filter as post",
|
||||
req: &scim.ListRequest{
|
||||
Schemas: []schemas.ScimSchemaType{schemas.IdSearchRequest},
|
||||
Count: gu.Ptr(5),
|
||||
StartIndex: gu.Ptr(1),
|
||||
SortOrder: gu.Ptr(scim.ListRequestSortOrderAsc),
|
||||
SortBy: gu.Ptr("username"),
|
||||
Filter: gu.Ptr(`emails sw "scim-email-1" and emails ew "@example.com"`),
|
||||
SendAsPost: true,
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
// sort the created users with usernames instead of creation date
|
||||
sortedResources := sortScimUserByUsername(resp.Resources)
|
||||
|
||||
assert.Equal(t, 5, resp.ItemsPerPage)
|
||||
assert.Equal(t, 2, resp.TotalResults)
|
||||
assert.Equal(t, 1, resp.StartIndex)
|
||||
assert.Len(t, sortedResources, 2)
|
||||
for _, resource := range sortedResources {
|
||||
assert.True(t, strings.HasPrefix(resource.UserName, "scim-username-1"))
|
||||
assert.Len(t, resource.Emails, 1)
|
||||
assert.True(t, strings.HasPrefix(resource.Emails[0].Value, "scim-email-1"))
|
||||
assert.True(t, strings.HasSuffix(resource.Emails[0].Value, "@example.com"))
|
||||
}
|
||||
assert.Equal(t, 5, resp.ItemsPerPage)
|
||||
assert.Equal(t, 2, resp.TotalResults)
|
||||
assert.Equal(t, 1, resp.StartIndex)
|
||||
assert.Len(t, sortedResources, 2)
|
||||
for _, resource := range sortedResources {
|
||||
assert.True(t, strings.HasPrefix(resource.UserName, "scim-username-1"))
|
||||
assert.Len(t, resource.Emails, 1)
|
||||
assert.True(t, strings.HasPrefix(resource.Emails[0].Value, "scim-email-1"))
|
||||
assert.True(t, strings.HasSuffix(resource.Emails[0].Value, "@example.com"))
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "count users without filter",
|
||||
// use other org, modifications of users happens only on primary org
|
||||
orgID: secondaryOrg.OrganizationId,
|
||||
ctx: iamOwnerCtx,
|
||||
prepare: func(t require.TestingT) *scim.ListRequest {
|
||||
return &scim.ListRequest{
|
||||
Count: gu.Ptr(0),
|
||||
}
|
||||
{
|
||||
name: "count users without filter",
|
||||
// use other org, modifications of users happens only on primary org
|
||||
orgID: secondaryOrg.OrganizationId,
|
||||
ctx: iamOwnerCtx,
|
||||
prepare: func(t require.TestingT) *scim.ListRequest {
|
||||
return &scim.ListRequest{
|
||||
Count: gu.Ptr(0),
|
||||
}
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Equal(t, 0, resp.ItemsPerPage)
|
||||
assert.Equal(t, totalCountOfHumanUsers, resp.TotalResults)
|
||||
assert.Equal(t, 1, resp.StartIndex)
|
||||
assert.Len(t, resp.Resources, 0)
|
||||
},
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Equal(t, 0, resp.ItemsPerPage)
|
||||
assert.Equal(t, totalCountOfHumanUsers, resp.TotalResults)
|
||||
assert.Equal(t, 1, resp.StartIndex)
|
||||
assert.Len(t, resp.Resources, 0)
|
||||
{
|
||||
name: "list users with active filter",
|
||||
req: &scim.ListRequest{
|
||||
Filter: gu.Ptr(`active eq false`),
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Equal(t, 100, resp.ItemsPerPage)
|
||||
assert.Equal(t, 1, resp.TotalResults)
|
||||
assert.Equal(t, 1, resp.StartIndex)
|
||||
assert.Len(t, resp.Resources, 1)
|
||||
assert.True(t, strings.HasPrefix(resp.Resources[0].UserName, "scim-username-0"))
|
||||
assert.False(t, resp.Resources[0].Active.Bool())
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list users with active filter",
|
||||
req: &scim.ListRequest{
|
||||
Filter: gu.Ptr(`active eq false`),
|
||||
{
|
||||
name: "list users with externalid filter",
|
||||
req: &scim.ListRequest{
|
||||
Filter: gu.Ptr(`externalid eq "701984"`),
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Equal(t, 100, resp.ItemsPerPage)
|
||||
assert.Equal(t, 1, resp.TotalResults)
|
||||
assert.Equal(t, 1, resp.StartIndex)
|
||||
assert.Len(t, resp.Resources, 1)
|
||||
assert.Equal(t, resp.Resources[0].ExternalID, "701984")
|
||||
},
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Equal(t, 100, resp.ItemsPerPage)
|
||||
assert.Equal(t, 1, resp.TotalResults)
|
||||
assert.Equal(t, 1, resp.StartIndex)
|
||||
assert.Len(t, resp.Resources, 1)
|
||||
assert.True(t, strings.HasPrefix(resp.Resources[0].UserName, "scim-username-0"))
|
||||
assert.False(t, resp.Resources[0].Active.Bool())
|
||||
{
|
||||
name: "list users with externalid filter invalid operator",
|
||||
req: &scim.ListRequest{
|
||||
Filter: gu.Ptr(`externalid pr`),
|
||||
},
|
||||
wantErr: true,
|
||||
errorType: "invalidFilter",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list users with externalid filter",
|
||||
req: &scim.ListRequest{
|
||||
Filter: gu.Ptr(`externalid eq "701984"`),
|
||||
{
|
||||
name: "list users with externalid complex filter",
|
||||
req: &scim.ListRequest{
|
||||
Filter: gu.Ptr(`externalid eq "701984" and username eq "bjensen@example.com"`),
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Equal(t, 100, resp.ItemsPerPage)
|
||||
assert.Equal(t, 1, resp.TotalResults)
|
||||
assert.Equal(t, 1, resp.StartIndex)
|
||||
assert.Len(t, resp.Resources, 1)
|
||||
assert.Equal(t, resp.Resources[0].UserName, "bjensen@example.com")
|
||||
assert.Equal(t, resp.Resources[0].ExternalID, "701984")
|
||||
},
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Equal(t, 100, resp.ItemsPerPage)
|
||||
assert.Equal(t, 1, resp.TotalResults)
|
||||
assert.Equal(t, 1, resp.StartIndex)
|
||||
assert.Len(t, resp.Resources, 1)
|
||||
assert.Equal(t, resp.Resources[0].ExternalID, "701984")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list users with externalid filter invalid operator",
|
||||
req: &scim.ListRequest{
|
||||
Filter: gu.Ptr(`externalid pr`),
|
||||
},
|
||||
wantErr: true,
|
||||
errorType: "invalidFilter",
|
||||
},
|
||||
{
|
||||
name: "list users with externalid complex filter",
|
||||
req: &scim.ListRequest{
|
||||
Filter: gu.Ptr(`externalid eq "701984" and username eq "bjensen@example.com"`),
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Equal(t, 100, resp.ItemsPerPage)
|
||||
assert.Equal(t, 1, resp.TotalResults)
|
||||
assert.Equal(t, 1, resp.StartIndex)
|
||||
assert.Len(t, resp.Resources, 1)
|
||||
assert.Equal(t, resp.Resources[0].UserName, "bjensen@example.com")
|
||||
assert.Equal(t, resp.Resources[0].ExternalID, "701984")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "count users with filter",
|
||||
req: &scim.ListRequest{
|
||||
Count: gu.Ptr(0),
|
||||
Filter: gu.Ptr(`emails sw "scim-email-1" and emails ew "@example.com"`),
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Equal(t, 0, resp.ItemsPerPage)
|
||||
assert.Equal(t, 2, resp.TotalResults)
|
||||
assert.Equal(t, 1, resp.StartIndex)
|
||||
assert.Len(t, resp.Resources, 0)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list users with modification date filter",
|
||||
prepare: func(t require.TestingT) *scim.ListRequest {
|
||||
userID := createdUserIDs[len(createdUserIDs)-1] // use the last entry, as we use the others for other assertions
|
||||
_, err := Instance.Client.UserV2.UpdateHumanUser(CTX, &user_v2.UpdateHumanUserRequest{
|
||||
UserId: userID,
|
||||
|
||||
Profile: &user_v2.SetHumanProfile{
|
||||
GivenName: "scim-user-given-name-modified-0: " + gofakeit.FirstName(),
|
||||
FamilyName: "scim-user-family-name-modified-0: " + gofakeit.LastName(),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return &scim.ListRequest{
|
||||
// filter by id too to exclude other random users
|
||||
Filter: gu.Ptr(fmt.Sprintf(`id eq "%s" and meta.LASTMODIFIED gt "%s"`, userID, testsInitializedUtc.Format(time.RFC3339))),
|
||||
}
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Len(t, resp.Resources, 1)
|
||||
assert.Equal(t, resp.Resources[0].ID, createdUserIDs[len(createdUserIDs)-1])
|
||||
assert.True(t, strings.HasPrefix(resp.Resources[0].Name.FamilyName, "scim-user-family-name-modified-0:"))
|
||||
assert.True(t, strings.HasPrefix(resp.Resources[0].Name.GivenName, "scim-user-given-name-modified-0:"))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list users with creation date filter",
|
||||
prepare: func(t require.TestingT) *scim.ListRequest {
|
||||
resp := createHumanUser(t, CTX, Instance.DefaultOrg.Id, 100)
|
||||
return &scim.ListRequest{
|
||||
Filter: gu.Ptr(fmt.Sprintf(`id eq "%s" and meta.created gt "%s"`, resp.UserId, testsInitializedUtc.Format(time.RFC3339))),
|
||||
}
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Len(t, resp.Resources, 1)
|
||||
assert.True(t, strings.HasPrefix(resp.Resources[0].UserName, "scim-username-100:"))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "validate returned objects",
|
||||
req: &scim.ListRequest{
|
||||
Filter: gu.Ptr(fmt.Sprintf(`id eq "%s"`, createdUserIDs[0])),
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Len(t, resp.Resources, 1)
|
||||
if !test.PartiallyDeepEqual(fullUser, resp.Resources[0]) {
|
||||
t.Errorf("got = %#v, want %#v", resp.Resources[0], fullUser)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "do not return user of other org",
|
||||
req: &scim.ListRequest{
|
||||
Filter: gu.Ptr(fmt.Sprintf(`id eq "%s"`, secondaryOrgCreatedUserIDs[0])),
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Len(t, resp.Resources, 0)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "do not count user of other org",
|
||||
prepare: func(t require.TestingT) *scim.ListRequest {
|
||||
iamOwnerCtx := Instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
org := Instance.CreateOrganization(iamOwnerCtx, gofakeit.Name(), gofakeit.Email())
|
||||
resp := createHumanUser(t, iamOwnerCtx, org.OrganizationId, 102)
|
||||
|
||||
return &scim.ListRequest{
|
||||
{
|
||||
name: "count users with filter",
|
||||
req: &scim.ListRequest{
|
||||
Count: gu.Ptr(0),
|
||||
Filter: gu.Ptr(fmt.Sprintf(`id eq "%s"`, resp.UserId)),
|
||||
Filter: gu.Ptr(`emails sw "scim-email-1" and emails ew "@example.com"`),
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Equal(t, 0, resp.ItemsPerPage)
|
||||
assert.Equal(t, 2, resp.TotalResults)
|
||||
assert.Equal(t, 1, resp.StartIndex)
|
||||
assert.Len(t, resp.Resources, 0)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list users with modification date filter",
|
||||
prepare: func(t require.TestingT) *scim.ListRequest {
|
||||
userID := createdUserIDs[len(createdUserIDs)-1] // use the last entry, as we use the others for other assertions
|
||||
_, err := Instance.Client.UserV2.UpdateHumanUser(CTX, &user_v2.UpdateHumanUserRequest{
|
||||
UserId: userID,
|
||||
|
||||
Profile: &user_v2.SetHumanProfile{
|
||||
GivenName: "scim-user-given-name-modified-0: " + gofakeit.FirstName(),
|
||||
FamilyName: "scim-user-family-name-modified-0: " + gofakeit.LastName(),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
return &scim.ListRequest{
|
||||
// filter by id too to exclude other random users
|
||||
Filter: gu.Ptr(fmt.Sprintf(`id eq "%s" and meta.LASTMODIFIED gt "%s"`, userID, testsInitializedUtc.Format(time.RFC3339))),
|
||||
}
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Len(t, resp.Resources, 1)
|
||||
assert.Equal(t, resp.Resources[0].ID, createdUserIDs[len(createdUserIDs)-1])
|
||||
assert.True(t, strings.HasPrefix(resp.Resources[0].Name.FamilyName, "scim-user-family-name-modified-0:"))
|
||||
assert.True(t, strings.HasPrefix(resp.Resources[0].Name.GivenName, "scim-user-given-name-modified-0:"))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "list users with creation date filter",
|
||||
prepare: func(t require.TestingT) *scim.ListRequest {
|
||||
resp := createHumanUser(t, CTX, Instance.DefaultOrg.Id, 100)
|
||||
return &scim.ListRequest{
|
||||
Filter: gu.Ptr(fmt.Sprintf(`id eq "%s" and meta.created gt "%s"`, resp.UserId, testsInitializedUtc.Format(time.RFC3339))),
|
||||
}
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Len(t, resp.Resources, 1)
|
||||
assert.True(t, strings.HasPrefix(resp.Resources[0].UserName, "scim-username-100:"))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "validate returned objects",
|
||||
req: &scim.ListRequest{
|
||||
Filter: gu.Ptr(fmt.Sprintf(`id eq "%s"`, createdUserIDs[0])),
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Len(t, resp.Resources, 1)
|
||||
if !test.PartiallyDeepEqual(fullUser, resp.Resources[0]) {
|
||||
t.Errorf("got = %#v, want %#v", resp.Resources[0], fullUser)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "do not return user of other org",
|
||||
req: &scim.ListRequest{
|
||||
Filter: gu.Ptr(fmt.Sprintf(`id eq "%s"`, secondaryOrgCreatedUserIDs[0])),
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Len(t, resp.Resources, 0)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "do not count user of other org",
|
||||
prepare: func(t require.TestingT) *scim.ListRequest {
|
||||
iamOwnerCtx := Instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
org := Instance.CreateOrganization(iamOwnerCtx, gofakeit.Name(), gofakeit.Email())
|
||||
resp := createHumanUser(t, iamOwnerCtx, org.OrganizationId, 102)
|
||||
|
||||
return &scim.ListRequest{
|
||||
Count: gu.Ptr(0),
|
||||
Filter: gu.Ptr(fmt.Sprintf(`id eq "%s"`, resp.UserId)),
|
||||
}
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Len(t, resp.Resources, 0)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "scoped externalID",
|
||||
prepare: func(t require.TestingT) *scim.ListRequest {
|
||||
resp := createHumanUser(t, CTX, Instance.DefaultOrg.Id, 102)
|
||||
|
||||
// set provisioning domain of service user
|
||||
setProvisioningDomain(t, Instance.Users.Get(integration.UserTypeOrgOwner).ID, "fooBar")
|
||||
|
||||
// set externalID for provisioning domain
|
||||
setAndEnsureMetadata(t, resp.UserId, "urn:zitadel:scim:fooBar:externalId", "100-scopedExternalId")
|
||||
return &scim.ListRequest{
|
||||
Filter: gu.Ptr(fmt.Sprintf(`id eq "%s"`, resp.UserId)),
|
||||
}
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Len(t, resp.Resources, 1)
|
||||
assert.Equal(t, resp.Resources[0].ExternalID, "100-scopedExternalId")
|
||||
},
|
||||
cleanup: func(t require.TestingT) {
|
||||
// delete provisioning domain of service user
|
||||
removeProvisioningDomain(t, Instance.Users.Get(integration.UserTypeOrgOwner).ID)
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.ctx == nil {
|
||||
tt.ctx = CTX
|
||||
}
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Len(t, resp.Resources, 0)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "scoped externalID",
|
||||
prepare: func(t require.TestingT) *scim.ListRequest {
|
||||
resp := createHumanUser(t, CTX, Instance.DefaultOrg.Id, 102)
|
||||
|
||||
// set provisioning domain of service user
|
||||
setProvisioningDomain(t, Instance.Users.Get(integration.UserTypeOrgOwner).ID, "fooBar")
|
||||
|
||||
// set externalID for provisioning domain
|
||||
setAndEnsureMetadata(t, resp.UserId, "urn:zitadel:scim:fooBar:externalId", "100-scopedExternalId")
|
||||
return &scim.ListRequest{
|
||||
Filter: gu.Ptr(fmt.Sprintf(`id eq "%s"`, resp.UserId)),
|
||||
if tt.prepare != nil {
|
||||
tt.req = tt.prepare(t)
|
||||
}
|
||||
},
|
||||
assert: func(t assert.TestingT, resp *scim.ListResponse[*resources.ScimUser]) {
|
||||
assert.Len(t, resp.Resources, 1)
|
||||
assert.Equal(t, resp.Resources[0].ExternalID, "100-scopedExternalId")
|
||||
},
|
||||
cleanup: func(t require.TestingT) {
|
||||
// delete provisioning domain of service user
|
||||
removeProvisioningDomain(t, Instance.Users.Get(integration.UserTypeOrgOwner).ID)
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.ctx == nil {
|
||||
tt.ctx = CTX
|
||||
}
|
||||
|
||||
if tt.prepare != nil {
|
||||
tt.req = tt.prepare(t)
|
||||
}
|
||||
if tt.orgID == "" {
|
||||
tt.orgID = Instance.DefaultOrg.Id
|
||||
}
|
||||
|
||||
if tt.orgID == "" {
|
||||
tt.orgID = Instance.DefaultOrg.Id
|
||||
}
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tt.ctx, time.Minute)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
listResp, err := Instance.Client.SCIM.Users.List(tt.ctx, tt.orgID, tt.req)
|
||||
if tt.wantErr {
|
||||
statusCode := tt.errorStatus
|
||||
if statusCode == 0 {
|
||||
statusCode = http.StatusBadRequest
|
||||
}
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(tt.ctx, time.Minute)
|
||||
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
listResp, err := Instance.Client.SCIM.Users.List(tt.ctx, tt.orgID, tt.req)
|
||||
if tt.wantErr {
|
||||
statusCode := tt.errorStatus
|
||||
if statusCode == 0 {
|
||||
statusCode = http.StatusBadRequest
|
||||
scimErr := scim.RequireScimError(ttt, statusCode, err)
|
||||
if tt.errorType != "" {
|
||||
assert.Equal(t, tt.errorType, scimErr.Error.ScimType)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
scimErr := scim.RequireScimError(ttt, statusCode, err)
|
||||
if tt.errorType != "" {
|
||||
assert.Equal(t, tt.errorType, scimErr.Error.ScimType)
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(ttt, []schemas.ScimSchemaType{"urn:ietf:params:scim:api:messages:2.0:ListResponse"}, listResp.Schemas)
|
||||
if tt.assert != nil {
|
||||
tt.assert(ttt, listResp)
|
||||
}
|
||||
return
|
||||
}
|
||||
}, retryDuration, tick)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.EqualValues(ttt, []schemas.ScimSchemaType{"urn:ietf:params:scim:api:messages:2.0:ListResponse"}, listResp.Schemas)
|
||||
if tt.assert != nil {
|
||||
tt.assert(ttt, listResp)
|
||||
if tt.cleanup != nil {
|
||||
tt.cleanup(t)
|
||||
}
|
||||
}, retryDuration, tick)
|
||||
|
||||
if tt.cleanup != nil {
|
||||
tt.cleanup(t)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
func sortScimUserByUsername(users []*resources.ScimUser) []*resources.ScimUser {
|
||||
sortedResources := users
|
||||
slices.SortFunc(sortedResources, func(a, b *resources.ScimUser) int {
|
||||
|
Reference in New Issue
Block a user