From b4584907d7a463b64e3737d286f2e0d9b46ff4f6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 20:11:39 +0000 Subject: [PATCH] Bump github.com/conductorone/baton-sdk from 0.4.8 to 0.4.11 Bumps [github.com/conductorone/baton-sdk](https://github.com/conductorone/baton-sdk) from 0.4.8 to 0.4.11. - [Release notes](https://github.com/conductorone/baton-sdk/releases) - [Commits](https://github.com/conductorone/baton-sdk/compare/v0.4.8...v0.4.11) --- updated-dependencies: - dependency-name: github.com/conductorone/baton-sdk dependency-version: 0.4.11 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- go.mod | 4 +- go.sum | 4 +- .../baton-sdk/pkg/actions/actions.go | 2 +- .../conductorone/baton-sdk/pkg/cli/cli.go | 5 +- .../baton-sdk/pkg/cli/commands.go | 4 +- .../baton-sdk/pkg/cli/lambda_server__added.go | 26 +- .../pkg/connectorbuilder/accounts.go | 120 ++ .../baton-sdk/pkg/connectorbuilder/actions.go | 167 ++ .../baton-sdk/pkg/connectorbuilder/assets.go | 11 + .../pkg/connectorbuilder/connectorbuilder.go | 1473 ++--------------- .../pkg/connectorbuilder/credentials.go | 101 ++ .../baton-sdk/pkg/connectorbuilder/events.go | 155 ++ .../pkg/connectorbuilder/resource_manager.go | 213 +++ .../connectorbuilder/resource_provisioner.go | 167 ++ .../pkg/connectorbuilder/resource_syncer.go | 228 +++ .../baton-sdk/pkg/connectorbuilder/tickets.go | 238 +++ .../baton-sdk/pkg/field/fields.go | 21 + .../conductorone/baton-sdk/pkg/sdk/version.go | 2 +- .../baton-sdk/pkg/session/grpc_session.go | 20 +- .../baton-sdk/pkg/session/json.go | 16 +- .../baton-sdk/pkg/session/memory.go | 20 +- .../baton-sdk/pkg/session/session.go | 20 +- .../conductorone/baton-sdk/pkg/sync/syncer.go | 10 +- .../baton-sdk/pkg/synccompactor/compactor.go | 1 + .../baton-sdk/pkg/types/session_cache.go | 58 - .../baton-sdk/pkg/types/sessions/sessions.go | 52 + .../baton-sdk/pkg/ugrpc/interceptors.go | 12 +- .../baton-sdk/pkg/uotel/config.go | 2 +- vendor/modules.txt | 5 +- 29 files changed, 1672 insertions(+), 1485 deletions(-) create mode 100644 vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/accounts.go create mode 100644 vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/actions.go create mode 100644 vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/credentials.go create mode 100644 vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/events.go create mode 100644 vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/resource_manager.go create mode 100644 vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/resource_provisioner.go create mode 100644 vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/resource_syncer.go create mode 100644 vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/tickets.go delete mode 100644 vendor/github.com/conductorone/baton-sdk/pkg/types/session_cache.go create mode 100644 vendor/github.com/conductorone/baton-sdk/pkg/types/sessions/sessions.go diff --git a/go.mod b/go.mod index 1ad52336..25ed59e3 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ module github.com/conductorone/baton-sql -go 1.25 +go 1.25.2 require ( github.com/SAP/go-hdb v1.14.5 - github.com/conductorone/baton-sdk v0.4.8 + github.com/conductorone/baton-sdk v0.4.11 github.com/elliotchance/phpserialize v1.4.0 github.com/ennyjfrick/ruleguard-logfatal v0.0.2 github.com/go-sql-driver/mysql v1.9.2 diff --git a/go.sum b/go.sum index 1c9ab669..3bd37188 100644 --- a/go.sum +++ b/go.sum @@ -78,8 +78,8 @@ github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyY github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/conductorone/baton-sdk v0.4.8 h1:bK32ANhtllTy0Obo7vugdMO7ebifzDe0JePgeiLzgwg= -github.com/conductorone/baton-sdk v0.4.8/go.mod h1:EYZ1Hq2NCSENzFLuTPdnarSXfb7ESjeXwDvDe0TPR3s= +github.com/conductorone/baton-sdk v0.4.11 h1:WmPsk1vZ+9SkjkqqWPr2suT80I12qhw8B33b5rwGiFY= +github.com/conductorone/baton-sdk v0.4.11/go.mod h1:UYJ9rgXowKzlHFSyDwh3NISWrDHZhf+yR4LNJLWMHUg= github.com/conductorone/dpop v0.2.4 h1:PaiDOX1gAIXtOJPxXf08GsGkpCuT/iECEjSJzLpi0zU= github.com/conductorone/dpop v0.2.4/go.mod h1:gyo8TtzB9SCFCsjsICH4IaLZ7y64CcrDXMOPBwfq/3s= github.com/conductorone/dpop/integrations/dpop_grpc v0.2.4 h1:lYxYi9/WTSL9sE96CO0QF2BY3kehs8dTTApI134TGCA= diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/actions/actions.go b/vendor/github.com/conductorone/baton-sdk/pkg/actions/actions.go index a78c349b..58561873 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/actions/actions.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/actions/actions.go @@ -233,7 +233,7 @@ func (a *ActionManager) InvokeAction(ctx context.Context, name string, args *str // If handler takes longer than an hour, return status failed. go func() { oa.SetStatus(ctx, v2.BatonActionStatus_BATON_ACTION_STATUS_RUNNING) - handlerCtx, cancel := context.WithTimeoutCause(ctx, 1*time.Hour, errors.New("action handler timed out")) + handlerCtx, cancel := context.WithTimeoutCause(context.Background(), 1*time.Hour, errors.New("action handler timed out")) defer cancel() var oaErr error oa.Rv, oa.Annos, oaErr = handler(handlerCtx, args) diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/cli/cli.go b/vendor/github.com/conductorone/baton-sdk/pkg/cli/cli.go index 32b0541f..b1cb3bfc 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/cli/cli.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/cli/cli.go @@ -7,6 +7,7 @@ import ( "github.com/conductorone/baton-sdk/pkg/field" "github.com/conductorone/baton-sdk/pkg/types" + "github.com/conductorone/baton-sdk/pkg/types/sessions" "github.com/spf13/cobra" "github.com/spf13/pflag" "github.com/spf13/viper" @@ -17,12 +18,12 @@ import ( type GetConnectorFunc[T field.Configurable] func(ctx context.Context, cfg T) (types.ConnectorServer, error) // WithSessionCache creates a session cache using the provided constructor and adds it to the context. -func WithSessionCache(ctx context.Context, constructor types.SessionCacheConstructor) (context.Context, error) { +func WithSessionCache(ctx context.Context, constructor sessions.SessionStoreConstructor) (context.Context, error) { sessionCache, err := constructor(ctx) if err != nil { return ctx, fmt.Errorf("failed to create session cache: %w", err) } - return context.WithValue(ctx, types.SessionCacheKey{}, sessionCache), nil + return context.WithValue(ctx, sessions.SessionStoreKey{}, sessionCache), nil } func MakeGenericConfiguration[T field.Configurable](v *viper.Viper) (T, error) { diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/cli/commands.go b/vendor/github.com/conductorone/baton-sdk/pkg/cli/commands.go index 3f728655..5d0d015e 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/cli/commands.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/cli/commands.go @@ -27,7 +27,7 @@ import ( "github.com/conductorone/baton-sdk/pkg/field" "github.com/conductorone/baton-sdk/pkg/logging" "github.com/conductorone/baton-sdk/pkg/session" - "github.com/conductorone/baton-sdk/pkg/types" + "github.com/conductorone/baton-sdk/pkg/types/sessions" "github.com/conductorone/baton-sdk/pkg/uotel" ) @@ -38,7 +38,7 @@ const ( type ContrainstSetter func(*cobra.Command, field.Configuration) error // defaultSessionCacheConstructor creates a default in-memory session cache. -func defaultSessionCacheConstructor(ctx context.Context, opt ...types.SessionCacheConstructorOption) (types.SessionCache, error) { +func defaultSessionCacheConstructor(ctx context.Context, opt ...sessions.SessionStoreConstructorOption) (sessions.SessionStore, error) { return session.NewMemorySessionCache(ctx, opt...) } diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/cli/lambda_server__added.go b/vendor/github.com/conductorone/baton-sdk/pkg/cli/lambda_server__added.go index 28ca3713..e19f17a4 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/cli/lambda_server__added.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/cli/lambda_server__added.go @@ -29,7 +29,7 @@ import ( c1_lambda_config "github.com/conductorone/baton-sdk/pkg/lambda/grpc/config" "github.com/conductorone/baton-sdk/pkg/lambda/grpc/middleware" "github.com/conductorone/baton-sdk/pkg/session" - "github.com/conductorone/baton-sdk/pkg/types" + "github.com/conductorone/baton-sdk/pkg/types/sessions" "google.golang.org/grpc" ) @@ -67,12 +67,16 @@ func OptionallyAddLambdaCommand[T field.Configurable]( logLevel = "info" } - initalLogFields := map[string]interface{}{ - "tenant": os.Getenv("tenant"), - "connector": os.Getenv("connector"), - "installation": os.Getenv("installation"), - "app": os.Getenv("app"), - "version": os.Getenv("version"), + initialLogFields := map[string]interface{}{ + "tenant_id": os.Getenv("tenant"), + "connector_id": os.Getenv("connector"), + "app_id": os.Getenv("app"), + "release_version": os.Getenv("version"), + "installation": os.Getenv("installation"), + "catalog_id": os.Getenv("catalog_id"), + "catalog_name": os.Getenv("catalog_name"), + "tenant_name": os.Getenv("tenant_name"), + "tenant_is_internal": os.Getenv("tenant_is_internal"), } runCtx, err := initLogger( @@ -80,13 +84,13 @@ func OptionallyAddLambdaCommand[T field.Configurable]( name, logging.WithLogFormat(v.GetString("log-format")), logging.WithLogLevel(logLevel), - logging.WithInitialFields(initalLogFields), + logging.WithInitialFields(initialLogFields), ) if err != nil { return err } - runCtx, otelShutdown, err := initOtel(runCtx, name, v, initalLogFields) + runCtx, otelShutdown, err := initOtel(runCtx, name, v, initialLogFields) if err != nil { return err } @@ -223,8 +227,8 @@ func OptionallyAddLambdaCommand[T field.Configurable]( } // createSessionCacheConstructor creates a session cache constructor function that uses the provided gRPC client -func createSessionCacheConstructor(grpcClient grpc.ClientConnInterface) types.SessionCacheConstructor { - return func(ctx context.Context, opt ...types.SessionCacheConstructorOption) (types.SessionCache, error) { +func createSessionCacheConstructor(grpcClient grpc.ClientConnInterface) sessions.SessionStoreConstructor { + return func(ctx context.Context, opt ...sessions.SessionStoreConstructorOption) (sessions.SessionStore, error) { // Create the gRPC session client using the same gRPC connection client := pb_connector_api.NewBatonSessionServiceClient(grpcClient) // Create and return the session cache diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/accounts.go b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/accounts.go new file mode 100644 index 00000000..e796fa72 --- /dev/null +++ b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/accounts.go @@ -0,0 +1,120 @@ +package connectorbuilder + +import ( + "context" + "fmt" + + v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" + "github.com/conductorone/baton-sdk/pkg/annotations" + "github.com/conductorone/baton-sdk/pkg/crypto" + "github.com/conductorone/baton-sdk/pkg/types/tasks" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" +) + +// CreateAccountResponse is a semi-opaque type returned from CreateAccount operations. +// +// This is used to communicate the result of account creation back to Baton. +type CreateAccountResponse interface { + proto.Message + GetIsCreateAccountResult() bool +} + +// AccountManager extends ResourceSyncer to add capabilities for managing user accounts. +// +// Implementing this interface indicates the connector supports creating accounts +// in the external system. A resource type should implement this interface if it +// represents users or accounts that can be provisioned. +type AccountManager interface { + ResourceSyncer + CreateAccount(ctx context.Context, + accountInfo *v2.AccountInfo, + credentialOptions *v2.LocalCredentialOptions) (CreateAccountResponse, []*v2.PlaintextData, annotations.Annotations, error) + CreateAccountCapabilityDetails(ctx context.Context) (*v2.CredentialDetailsAccountProvisioning, annotations.Annotations, error) +} + +type OldAccountManager interface { + CreateAccount(ctx context.Context, + accountInfo *v2.AccountInfo, + credentialOptions *v2.CredentialOptions) (CreateAccountResponse, []*v2.PlaintextData, annotations.Annotations, error) +} + +func (b *builder) CreateAccount(ctx context.Context, request *v2.CreateAccountRequest) (*v2.CreateAccountResponse, error) { + ctx, span := tracer.Start(ctx, "builder.CreateAccount") + defer span.End() + + start := b.nowFunc() + tt := tasks.CreateAccountType + l := ctxzap.Extract(ctx) + if b.accountManager == nil { + l.Error("error: connector does not have account manager configured") + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, status.Error(codes.Unimplemented, "connector does not have account manager configured") + } + + opts, err := crypto.ConvertCredentialOptions(ctx, b.clientSecret, request.GetCredentialOptions(), request.GetEncryptionConfigs()) + if err != nil { + l.Error("error: converting credential options failed", zap.Error(err)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: converting credential options failed: %w", err) + } + + result, plaintexts, annos, err := b.accountManager.CreateAccount(ctx, request.GetAccountInfo(), opts) + if err != nil { + l.Error("error: create account failed", zap.Error(err)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: create account failed: %w", err) + } + + pkem, err := crypto.NewEncryptionManager(request.GetCredentialOptions(), request.GetEncryptionConfigs()) + if err != nil { + l.Error("error: creating encryption manager failed", zap.Error(err)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: creating encryption manager failed: %w", err) + } + + var encryptedDatas []*v2.EncryptedData + for _, plaintextCredential := range plaintexts { + encryptedData, err := pkem.Encrypt(ctx, plaintextCredential) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, err + } + encryptedDatas = append(encryptedDatas, encryptedData...) + } + + rv := &v2.CreateAccountResponse{ + EncryptedData: encryptedDatas, + Annotations: annos, + } + + switch r := result.(type) { + case *v2.CreateAccountResponse_SuccessResult: + rv.Result = &v2.CreateAccountResponse_Success{Success: r} + case *v2.CreateAccountResponse_ActionRequiredResult: + rv.Result = &v2.CreateAccountResponse_ActionRequired{ActionRequired: r} + default: + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, status.Error(codes.Unimplemented, fmt.Sprintf("unknown result type: %T", result)) + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return rv, nil +} + +func (b *builder) addAccountManager(_ context.Context, typeId string, rb ResourceSyncer) error { + if _, ok := rb.(OldAccountManager); ok { + return fmt.Errorf("error: old account manager interface implemented for %s", typeId) + } + + if accountManager, ok := rb.(AccountManager); ok { + if b.accountManager != nil { + return fmt.Errorf("error: duplicate resource type found for account manager %s", typeId) + } + b.accountManager = accountManager + } + return nil +} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/actions.go b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/actions.go new file mode 100644 index 00000000..92d27397 --- /dev/null +++ b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/actions.go @@ -0,0 +1,167 @@ +package connectorbuilder + +import ( + "context" + "fmt" + + v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" + "github.com/conductorone/baton-sdk/pkg/annotations" + "github.com/conductorone/baton-sdk/pkg/types/tasks" + "google.golang.org/protobuf/types/known/structpb" +) + +// CustomActionManager defines capabilities for handling custom actions. +// +// Note: RegisterActionManager is preferred for new connectors. +// +// This interface allows connectors to define and execute custom actions +// that can be triggered from Baton. +type CustomActionManager interface { + ListActionSchemas(ctx context.Context) ([]*v2.BatonActionSchema, annotations.Annotations, error) + GetActionSchema(ctx context.Context, name string) (*v2.BatonActionSchema, annotations.Annotations, error) + InvokeAction(ctx context.Context, name string, args *structpb.Struct) (string, v2.BatonActionStatus, *structpb.Struct, annotations.Annotations, error) + GetActionStatus(ctx context.Context, id string) (v2.BatonActionStatus, string, *structpb.Struct, annotations.Annotations, error) +} + +// RegisterActionManager extends ConnectorBuilder to add capabilities for registering custom actions. +// +// This is the recommended interface for implementing custom action support in new connectors. +// It provides a mechanism to register a CustomActionManager with the connector. +type RegisterActionManager interface { + ConnectorBuilder + RegisterActionManager(ctx context.Context) (CustomActionManager, error) +} + +func (b *builder) ListActionSchemas(ctx context.Context, request *v2.ListActionSchemasRequest) (*v2.ListActionSchemasResponse, error) { + ctx, span := tracer.Start(ctx, "builder.ListActionSchemas") + defer span.End() + + start := b.nowFunc() + tt := tasks.ActionListSchemasType + if b.actionManager == nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: action manager not implemented") + } + + actionSchemas, annos, err := b.actionManager.ListActionSchemas(ctx) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: listing action schemas failed: %w", err) + } + + rv := &v2.ListActionSchemasResponse{ + Schemas: actionSchemas, + Annotations: annos, + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return rv, nil +} + +func (b *builder) GetActionSchema(ctx context.Context, request *v2.GetActionSchemaRequest) (*v2.GetActionSchemaResponse, error) { + ctx, span := tracer.Start(ctx, "builder.GetActionSchema") + defer span.End() + + start := b.nowFunc() + tt := tasks.ActionGetSchemaType + if b.actionManager == nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: action manager not implemented") + } + + actionSchema, annos, err := b.actionManager.GetActionSchema(ctx, request.GetName()) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: getting action schema failed: %w", err) + } + + rv := &v2.GetActionSchemaResponse{ + Schema: actionSchema, + Annotations: annos, + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return rv, nil +} + +func (b *builder) InvokeAction(ctx context.Context, request *v2.InvokeActionRequest) (*v2.InvokeActionResponse, error) { + ctx, span := tracer.Start(ctx, "builder.InvokeAction") + defer span.End() + + start := b.nowFunc() + tt := tasks.ActionInvokeType + if b.actionManager == nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: action manager not implemented") + } + + id, status, resp, annos, err := b.actionManager.InvokeAction(ctx, request.GetName(), request.GetArgs()) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: invoking action failed: %w", err) + } + + rv := &v2.InvokeActionResponse{ + Id: id, + Name: request.GetName(), + Status: status, + Annotations: annos, + Response: resp, + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return rv, nil +} + +func (b *builder) GetActionStatus(ctx context.Context, request *v2.GetActionStatusRequest) (*v2.GetActionStatusResponse, error) { + ctx, span := tracer.Start(ctx, "builder.GetActionStatus") + defer span.End() + + start := b.nowFunc() + tt := tasks.ActionStatusType + if b.actionManager == nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: action manager not implemented") + } + + status, name, rv, annos, err := b.actionManager.GetActionStatus(ctx, request.GetId()) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: getting action status failed: %w", err) + } + + resp := &v2.GetActionStatusResponse{ + Id: request.GetId(), + Name: name, + Status: status, + Annotations: annos, + Response: rv, + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return resp, nil +} + +func (b *builder) addActionManager(ctx context.Context, c ConnectorBuilder) error { + if actionManager, ok := c.(CustomActionManager); ok { + if b.actionManager != nil { + return fmt.Errorf("error: cannot set multiple action managers") + } + b.actionManager = actionManager + } + + if registerActionManager, ok := c.(RegisterActionManager); ok { + if b.actionManager != nil { + return fmt.Errorf("error: cannot register multiple action managers") + } + actionManager, err := registerActionManager.RegisterActionManager(ctx) + if err != nil { + return fmt.Errorf("error: registering action manager failed: %w", err) + } + if actionManager == nil { + return fmt.Errorf("error: action manager is nil") + } + b.actionManager = actionManager + } + return nil +} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/assets.go b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/assets.go index f6431512..d0e551c6 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/assets.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/assets.go @@ -1,5 +1,7 @@ package connectorbuilder +import v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" + // FIXME(jirwin): Come back to streaming assets soon. // // const ( @@ -40,3 +42,12 @@ package connectorbuilder // } // return nil // } + +// GetAsset streams the asset to the client. +// FIXME(jirwin): Asset streaming is disabled. +func (b *builder) GetAsset(request *v2.AssetServiceGetAssetRequest, server v2.AssetService_GetAssetServer) error { + _, span := tracer.Start(server.Context(), "builderImpl.GetAsset") + defer span.End() + + return nil +} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/connectorbuilder.go b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/connectorbuilder.go index 34ea77a8..12c466d1 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/connectorbuilder.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/connectorbuilder.go @@ -14,15 +14,11 @@ import ( "go.uber.org/zap" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/structpb" - "google.golang.org/protobuf/types/known/timestamppb" v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" "github.com/conductorone/baton-sdk/pkg/annotations" "github.com/conductorone/baton-sdk/pkg/crypto" "github.com/conductorone/baton-sdk/pkg/metrics" - "github.com/conductorone/baton-sdk/pkg/pagination" "github.com/conductorone/baton-sdk/pkg/retry" "github.com/conductorone/baton-sdk/pkg/sdk" "github.com/conductorone/baton-sdk/pkg/session" @@ -33,234 +29,6 @@ import ( var tracer = otel.Tracer("baton-sdk/pkg.connectorbuilder") -// ResourceSyncer is the primary interface for connector developers to implement. -// -// It defines the core functionality for synchronizing resources, entitlements, and grants -// from external systems into Baton. Every connector must implement at least this interface -// for each resource type it supports. -// -// Extensions to this interface include: -// - ResourceProvisioner/ResourceProvisionerV2: For adding/removing access -// - ResourceManager: For creating and managing resources -// - ResourceDeleter: For deleting resources -// - AccountManager: For account provisioning operations -// - CredentialManager: For credential rotation operations. -// - ResourceTargetedSyncer: For directly getting a resource supporting targeted sync. -type ResourceSyncer interface { - ResourceType(ctx context.Context) *v2.ResourceType - List(ctx context.Context, parentResourceID *v2.ResourceId, pToken *pagination.Token) ([]*v2.Resource, string, annotations.Annotations, error) - Entitlements(ctx context.Context, resource *v2.Resource, pToken *pagination.Token) ([]*v2.Entitlement, string, annotations.Annotations, error) - Grants(ctx context.Context, resource *v2.Resource, pToken *pagination.Token) ([]*v2.Grant, string, annotations.Annotations, error) -} - -// ResourceProvisioner extends ResourceSyncer to add capabilities for granting and revoking access. -// -// Note: ResourceProvisionerV2 is preferred for new connectors as it provides -// enhanced grant capabilities. -// -// Implementing this interface indicates the connector supports provisioning operations -// for the associated resource type. -type ResourceProvisioner interface { - ResourceSyncer - ResourceType(ctx context.Context) *v2.ResourceType - Grant(ctx context.Context, resource *v2.Resource, entitlement *v2.Entitlement) (annotations.Annotations, error) - Revoke(ctx context.Context, grant *v2.Grant) (annotations.Annotations, error) -} - -// ResourceProvisionerV2 extends ResourceSyncer to add capabilities for granting and revoking access -// with enhanced functionality compared to ResourceProvisioner. -// -// This is the recommended interface for implementing provisioning operations in new connectors. -// It differs from ResourceProvisioner by returning a list of grants from the Grant method. -type ResourceProvisionerV2 interface { - ResourceSyncer - ResourceType(ctx context.Context) *v2.ResourceType - Grant(ctx context.Context, resource *v2.Resource, entitlement *v2.Entitlement) ([]*v2.Grant, annotations.Annotations, error) - Revoke(ctx context.Context, grant *v2.Grant) (annotations.Annotations, error) -} - -// ResourceManager extends ResourceSyncer to add capabilities for creating resources. -// -// Implementing this interface indicates the connector supports creating and deleting resources -// of the associated resource type. A ResourceManager automatically provides ResourceDeleter -// functionality. -type ResourceManager interface { - ResourceSyncer - Create(ctx context.Context, resource *v2.Resource) (*v2.Resource, annotations.Annotations, error) - ResourceDeleter -} - -// ResourceManagerV2 extends ResourceSyncer to add capabilities for creating resources. -// -// This is the recommended interface for implementing resource creation operations in new connectors. -type ResourceManagerV2 interface { - ResourceSyncer - Create(ctx context.Context, resource *v2.Resource) (*v2.Resource, annotations.Annotations, error) - ResourceDeleterV2 -} - -// ResourceDeleter extends ResourceSyncer to add capabilities for deleting resources. -// -// Implementing this interface indicates the connector supports deleting resources -// of the associated resource type. -type ResourceDeleter interface { - ResourceSyncer - Delete(ctx context.Context, resourceId *v2.ResourceId) (annotations.Annotations, error) -} - -// ResourceDeleterV2 extends ResourceSyncer to add capabilities for deleting resources. -// -// This is the recommended interface for implementing resource deletion operations in new connectors. -// It differs from ResourceDeleter by having the resource, not just the id. -type ResourceDeleterV2 interface { - ResourceSyncer - Delete(ctx context.Context, resourceId *v2.ResourceId, parentResourceID *v2.ResourceId) (annotations.Annotations, error) -} - -// ResourceTargetedSyncer extends ResourceSyncer to add capabilities for directly syncing an individual resource -// -// Implementing this interface indicates the connector supports calling "get" on a resource -// of the associated resource type. -type ResourceTargetedSyncer interface { - ResourceSyncer - Get(ctx context.Context, resourceId *v2.ResourceId, parentResourceId *v2.ResourceId) (*v2.Resource, annotations.Annotations, error) -} - -// CreateAccountResponse is a semi-opaque type returned from CreateAccount operations. -// -// This is used to communicate the result of account creation back to Baton. -type CreateAccountResponse interface { - proto.Message - GetIsCreateAccountResult() bool -} - -// AccountManager extends ResourceSyncer to add capabilities for managing user accounts. -// -// Implementing this interface indicates the connector supports creating accounts -// in the external system. A resource type should implement this interface if it -// represents users or accounts that can be provisioned. -type AccountManager interface { - ResourceSyncer - CreateAccount(ctx context.Context, - accountInfo *v2.AccountInfo, - credentialOptions *v2.LocalCredentialOptions) (CreateAccountResponse, []*v2.PlaintextData, annotations.Annotations, error) - CreateAccountCapabilityDetails(ctx context.Context) (*v2.CredentialDetailsAccountProvisioning, annotations.Annotations, error) -} - -type OldAccountManager interface { - CreateAccount(ctx context.Context, - accountInfo *v2.AccountInfo, - credentialOptions *v2.CredentialOptions) (CreateAccountResponse, []*v2.PlaintextData, annotations.Annotations, error) -} - -// CredentialManager extends ResourceSyncer to add capabilities for managing credentials. -// -// Implementing this interface indicates the connector supports rotating credentials -// for resources of the associated type. This is commonly used for user accounts -// or service accounts that have rotatable credentials. -type CredentialManager interface { - ResourceSyncer - Rotate(ctx context.Context, - resourceId *v2.ResourceId, - credentialOptions *v2.LocalCredentialOptions) ([]*v2.PlaintextData, annotations.Annotations, error) - RotateCapabilityDetails(ctx context.Context) (*v2.CredentialDetailsCredentialRotation, annotations.Annotations, error) -} - -type OldCredentialManager interface { - Rotate(ctx context.Context, - resourceId *v2.ResourceId, - credentialOptions *v2.CredentialOptions) ([]*v2.PlaintextData, annotations.Annotations, error) -} - -// Compatibility interface lets us handle both EventFeed and EventProvider the same. -type EventLister interface { - ListEvents(ctx context.Context, earliestEvent *timestamppb.Timestamp, pToken *pagination.StreamToken) ([]*v2.Event, *pagination.StreamState, annotations.Annotations, error) -} - -// Deprecated: This interface is deprecated in favor of EventProviderV2 which supports -// multiple event feeds. Implementing this interface indicates the connector can provide -// a single stream of events from the external system, enabling near real-time updates -// in Baton. New connectors should implement EventProviderV2 instead. -type EventProvider interface { - ConnectorBuilder - EventLister -} - -// NewEventProviderV2 is a new interface that allows connectors to provide multiple event feeds. -// -// This is the recommended interface for implementing event feed support in new connectors. -type EventProviderV2 interface { - ConnectorBuilder - EventFeeds(ctx context.Context) []EventFeed -} - -// EventFeed is a single stream of events from the external system. -// -// EventFeedMetadata describes this feed, and a connector can have multiple feeds. -type EventFeed interface { - EventLister - EventFeedMetadata(ctx context.Context) *v2.EventFeedMetadata -} - -type oldEventFeedWrapper struct { - feed EventLister -} - -const ( - LegacyBatonFeedId = "baton_feed_event" -) - -func (e *oldEventFeedWrapper) EventFeedMetadata(ctx context.Context) *v2.EventFeedMetadata { - return &v2.EventFeedMetadata{ - Id: LegacyBatonFeedId, - SupportedEventTypes: []v2.EventType{v2.EventType_EVENT_TYPE_UNSPECIFIED}, - } -} - -func (e *oldEventFeedWrapper) ListEvents( - ctx context.Context, - earliestEvent *timestamppb.Timestamp, - pToken *pagination.StreamToken, -) ([]*v2.Event, *pagination.StreamState, annotations.Annotations, error) { - return e.feed.ListEvents(ctx, earliestEvent, pToken) -} - -// TicketManager extends ConnectorBuilder to add capabilities for ticket management. -// -// Implementing this interface indicates the connector can integrate with an external -// ticketing system, allowing Baton to create and track tickets in that system. -type TicketManager interface { - ConnectorBuilder - GetTicket(ctx context.Context, ticketId string) (*v2.Ticket, annotations.Annotations, error) - CreateTicket(ctx context.Context, ticket *v2.Ticket, schema *v2.TicketSchema) (*v2.Ticket, annotations.Annotations, error) - GetTicketSchema(ctx context.Context, schemaID string) (*v2.TicketSchema, annotations.Annotations, error) - ListTicketSchemas(ctx context.Context, pToken *pagination.Token) ([]*v2.TicketSchema, string, annotations.Annotations, error) - BulkCreateTickets(context.Context, *v2.TicketsServiceBulkCreateTicketsRequest) (*v2.TicketsServiceBulkCreateTicketsResponse, error) - BulkGetTickets(context.Context, *v2.TicketsServiceBulkGetTicketsRequest) (*v2.TicketsServiceBulkGetTicketsResponse, error) -} - -// CustomActionManager defines capabilities for handling custom actions. -// -// Note: RegisterActionManager is preferred for new connectors. -// -// This interface allows connectors to define and execute custom actions -// that can be triggered from Baton. -type CustomActionManager interface { - ListActionSchemas(ctx context.Context) ([]*v2.BatonActionSchema, annotations.Annotations, error) - GetActionSchema(ctx context.Context, name string) (*v2.BatonActionSchema, annotations.Annotations, error) - InvokeAction(ctx context.Context, name string, args *structpb.Struct) (string, v2.BatonActionStatus, *structpb.Struct, annotations.Annotations, error) - GetActionStatus(ctx context.Context, id string) (v2.BatonActionStatus, string, *structpb.Struct, annotations.Annotations, error) -} - -// RegisterActionManager extends ConnectorBuilder to add capabilities for registering custom actions. -// -// This is the recommended interface for implementing custom action support in new connectors. -// It provides a mechanism to register a CustomActionManager with the connector. -type RegisterActionManager interface { - ConnectorBuilder - RegisterActionManager(ctx context.Context) (CustomActionManager, error) -} - // ConnectorBuilder is the foundational interface for creating Baton connectors. // // This interface defines the core capabilities required by all connectors, including @@ -275,14 +43,11 @@ type ConnectorBuilder interface { ResourceSyncers(ctx context.Context) []ResourceSyncer } -type builderImpl struct { +type builder struct { resourceBuilders map[string]ResourceSyncer - resourceProvisioners map[string]ResourceProvisioner - resourceProvisionersV2 map[string]ResourceProvisionerV2 - resourceManagers map[string]ResourceManager - resourceManagersV2 map[string]ResourceManagerV2 - resourceDeleters map[string]ResourceDeleter - resourceDeletersV2 map[string]ResourceDeleterV2 + resourceProvisioners map[string]ResourceProvisionerV2 + resourceManagers map[string]ResourceManagerV2 + resourceDeleters map[string]ResourceDeleterV2 resourceTargetedSyncers map[string]ResourceTargetedSyncer accountManager AccountManager actionManager CustomActionManager @@ -296,207 +61,6 @@ type builderImpl struct { clientSecret *jose.JSONWebKey } -func (b *builderImpl) BulkCreateTickets(ctx context.Context, request *v2.TicketsServiceBulkCreateTicketsRequest) (*v2.TicketsServiceBulkCreateTicketsResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.BulkCreateTickets") - defer span.End() - - start := b.nowFunc() - tt := tasks.BulkCreateTicketsType - if b.ticketManager == nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: ticket manager not implemented") - } - - reqBody := request.GetTicketRequests() - if len(reqBody) == 0 { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: request body had no items") - } - - ticketsResponse, err := b.ticketManager.BulkCreateTickets(ctx, request) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: creating tickets failed: %w", err) - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.TicketsServiceBulkCreateTicketsResponse{ - Tickets: ticketsResponse.GetTickets(), - }, nil -} - -func (b *builderImpl) BulkGetTickets(ctx context.Context, request *v2.TicketsServiceBulkGetTicketsRequest) (*v2.TicketsServiceBulkGetTicketsResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.BulkGetTickets") - defer span.End() - - start := b.nowFunc() - tt := tasks.BulkGetTicketsType - if b.ticketManager == nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: ticket manager not implemented") - } - - reqBody := request.GetTicketRequests() - if len(reqBody) == 0 { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: request body had no items") - } - - ticketsResponse, err := b.ticketManager.BulkGetTickets(ctx, request) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: fetching tickets failed: %w", err) - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.TicketsServiceBulkGetTicketsResponse{ - Tickets: ticketsResponse.GetTickets(), - }, nil -} - -func (b *builderImpl) ListTicketSchemas(ctx context.Context, request *v2.TicketsServiceListTicketSchemasRequest) (*v2.TicketsServiceListTicketSchemasResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.ListTicketSchemas") - defer span.End() - - start := b.nowFunc() - tt := tasks.ListTicketSchemasType - if b.ticketManager == nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: ticket manager not implemented") - } - - retryer := retry.NewRetryer(ctx, retry.RetryConfig{ - MaxAttempts: 10, - InitialDelay: 15 * time.Second, - MaxDelay: 0, - }) - - for { - out, nextPageToken, annos, err := b.ticketManager.ListTicketSchemas(ctx, &pagination.Token{ - Size: int(request.PageSize), - Token: request.PageToken, - }) - if err == nil { - if request.PageToken != "" && request.PageToken == nextPageToken { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: listing ticket schemas failed: next page token is the same as the current page token. this is most likely a connector bug") - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.TicketsServiceListTicketSchemasResponse{ - List: out, - NextPageToken: nextPageToken, - Annotations: annos, - }, nil - } - if retryer.ShouldWaitAndRetry(ctx, err) { - continue - } - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: listing ticket schemas failed: %w", err) - } -} - -func (b *builderImpl) CreateTicket(ctx context.Context, request *v2.TicketsServiceCreateTicketRequest) (*v2.TicketsServiceCreateTicketResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.CreateTicket") - defer span.End() - - start := b.nowFunc() - tt := tasks.CreateTicketType - if b.ticketManager == nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: ticket manager not implemented") - } - - reqBody := request.GetRequest() - if reqBody == nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: request body is nil") - } - cTicket := &v2.Ticket{ - DisplayName: reqBody.GetDisplayName(), - Description: reqBody.GetDescription(), - Status: reqBody.GetStatus(), - Labels: reqBody.GetLabels(), - CustomFields: reqBody.GetCustomFields(), - RequestedFor: reqBody.GetRequestedFor(), - } - - ticket, annos, err := b.ticketManager.CreateTicket(ctx, cTicket, request.GetSchema()) - var resp *v2.TicketsServiceCreateTicketResponse - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - if ticket != nil { - resp = &v2.TicketsServiceCreateTicketResponse{ - Ticket: ticket, - Annotations: annos, - } - } - return resp, fmt.Errorf("error: creating ticket failed: %w", err) - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.TicketsServiceCreateTicketResponse{ - Ticket: ticket, - Annotations: annos, - }, nil -} - -func (b *builderImpl) GetTicket(ctx context.Context, request *v2.TicketsServiceGetTicketRequest) (*v2.TicketsServiceGetTicketResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.GetTicket") - defer span.End() - - start := b.nowFunc() - tt := tasks.GetTicketType - if b.ticketManager == nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: ticket manager not implemented") - } - - var resp *v2.TicketsServiceGetTicketResponse - ticket, annos, err := b.ticketManager.GetTicket(ctx, request.GetId()) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - if ticket != nil { - resp = &v2.TicketsServiceGetTicketResponse{ - Ticket: ticket, - Annotations: annos, - } - } - return resp, fmt.Errorf("error: getting ticket failed: %w", err) - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.TicketsServiceGetTicketResponse{ - Ticket: ticket, - Annotations: annos, - }, nil -} - -func (b *builderImpl) GetTicketSchema(ctx context.Context, request *v2.TicketsServiceGetTicketSchemaRequest) (*v2.TicketsServiceGetTicketSchemaResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.GetTicketSchema") - defer span.End() - - start := b.nowFunc() - tt := tasks.GetTicketSchemaType - if b.ticketManager == nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: ticket manager not implemented") - } - - ticketSchema, annos, err := b.ticketManager.GetTicketSchema(ctx, request.GetId()) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: getting ticket metadata failed: %w", err) - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.TicketsServiceGetTicketSchemaResponse{ - Schema: ticketSchema, - Annotations: annos, - }, nil -} - // NewConnector creates a new ConnectorServer for a new resource. func NewConnector(ctx context.Context, in interface{}, opts ...Opt) (types.ConnectorServer, error) { switch c := in.(type) { @@ -504,14 +68,11 @@ func NewConnector(ctx context.Context, in interface{}, opts ...Opt) (types.Conne clientSecretValue := ctx.Value(crypto.ContextClientSecretKey) clientSecretJWK, _ := clientSecretValue.(*jose.JSONWebKey) - ret := &builderImpl{ + b := &builder{ resourceBuilders: make(map[string]ResourceSyncer), - resourceProvisioners: make(map[string]ResourceProvisioner), - resourceProvisionersV2: make(map[string]ResourceProvisionerV2), - resourceManagers: make(map[string]ResourceManager), - resourceManagersV2: make(map[string]ResourceManagerV2), - resourceDeleters: make(map[string]ResourceDeleter), - resourceDeletersV2: make(map[string]ResourceDeleterV2), + resourceProvisioners: make(map[string]ResourceProvisionerV2), + resourceManagers: make(map[string]ResourceManagerV2), + resourceDeleters: make(map[string]ResourceDeleterV2), resourceTargetedSyncers: make(map[string]ResourceTargetedSyncer), accountManager: nil, actionManager: nil, @@ -523,163 +84,55 @@ func NewConnector(ctx context.Context, in interface{}, opts ...Opt) (types.Conne clientSecret: clientSecretJWK, } - err := ret.options(opts...) + err := b.options(opts...) if err != nil { return nil, err } - if ret.m == nil { - ret.m = metrics.New(metrics.NewNoOpHandler(ctx)) - } - - if b, ok := c.(EventProviderV2); ok { - for _, ef := range b.EventFeeds(ctx) { - feedData := ef.EventFeedMetadata(ctx) - if feedData == nil { - return nil, fmt.Errorf("error: event feed metadata is nil") - } - if err := feedData.Validate(); err != nil { - return nil, fmt.Errorf("error: event feed metadata for %s is invalid: %w", feedData.Id, err) - } - if _, ok := ret.eventFeeds[feedData.Id]; ok { - return nil, fmt.Errorf("error: duplicate event feed id found: %s", feedData.Id) - } - ret.eventFeeds[feedData.Id] = ef - } - } - - if b, ok := c.(EventProvider); ok { - // Register the legacy Baton feed as a v2 event feed - // implementing both v1 and v2 event feeds is not supported. - if len(ret.eventFeeds) != 0 { - return nil, fmt.Errorf("error: using legacy event feed is not supported when using EventProviderV2") - } - ret.eventFeeds[LegacyBatonFeedId] = &oldEventFeedWrapper{ - feed: b, - } + if b.m == nil { + b.m = metrics.New(metrics.NewNoOpHandler(ctx)) } - if ticketManager, ok := c.(TicketManager); ok { - if ret.ticketManager != nil { - return nil, fmt.Errorf("error: cannot set multiple ticket managers") - } - ret.ticketManager = ticketManager + if err := b.addEventFeed(ctx, c); err != nil { + return nil, err } - if actionManager, ok := c.(CustomActionManager); ok { - if ret.actionManager != nil { - return nil, fmt.Errorf("error: cannot set multiple action managers") - } - ret.actionManager = actionManager + if err := b.addTicketManager(ctx, c); err != nil { + return nil, err } - if registerActionManager, ok := c.(RegisterActionManager); ok { - if ret.actionManager != nil { - return nil, fmt.Errorf("error: cannot register multiple action managers") - } - actionManager, err := registerActionManager.RegisterActionManager(ctx) - if err != nil { - return nil, fmt.Errorf("error: registering action manager failed: %w", err) - } - if actionManager == nil { - return nil, fmt.Errorf("error: action manager is nil") - } - ret.actionManager = actionManager + if err := b.addActionManager(ctx, c); err != nil { + return nil, err } for _, rb := range c.ResourceSyncers(ctx) { rType := rb.ResourceType(ctx) - if _, ok := ret.resourceBuilders[rType.Id]; ok { - return nil, fmt.Errorf("error: duplicate resource type found for resource builder %s", rType.Id) - } - ret.resourceBuilders[rType.Id] = rb - if err := validateProvisionerVersion(ctx, rb); err != nil { + if err := b.addResourceBuilders(ctx, rType.Id, rb); err != nil { return nil, err } - if provisioner, ok := rb.(ResourceProvisioner); ok { - if _, ok := ret.resourceProvisioners[rType.Id]; ok { - return nil, fmt.Errorf("error: duplicate resource type found for resource provisioner %s", rType.Id) - } - ret.resourceProvisioners[rType.Id] = provisioner - } - if provisioner, ok := rb.(ResourceProvisionerV2); ok { - if _, ok := ret.resourceProvisionersV2[rType.Id]; ok { - return nil, fmt.Errorf("error: duplicate resource type found for resource provisioner v2 %s", rType.Id) - } - ret.resourceProvisionersV2[rType.Id] = provisioner - } - if targetedSyncer, ok := rb.(ResourceTargetedSyncer); ok { - if _, ok := ret.resourceTargetedSyncers[rType.Id]; ok { - return nil, fmt.Errorf("error: duplicate resource type found for resource targeted syncer %s", rType.Id) - } - ret.resourceTargetedSyncers[rType.Id] = targetedSyncer - } - - if resourceManager, ok := rb.(ResourceManager); ok { - if _, ok := ret.resourceManagers[rType.Id]; ok { - return nil, fmt.Errorf("error: duplicate resource type found for resource manager %s", rType.Id) - } - ret.resourceManagers[rType.Id] = resourceManager - // Support DeleteResourceV2 if connector implements both Create and Delete - if _, ok := ret.resourceDeleters[rType.Id]; ok { - // This should never happen - return nil, fmt.Errorf("error: duplicate resource type found for resource deleter %s", rType.Id) - } - ret.resourceDeleters[rType.Id] = resourceManager - } else { - if resourceDeleter, ok := rb.(ResourceDeleter); ok { - if _, ok := ret.resourceDeleters[rType.Id]; ok { - return nil, fmt.Errorf("error: duplicate resource type found for resource deleter %s", rType.Id) - } - ret.resourceDeleters[rType.Id] = resourceDeleter - } - } - - if resourceManager, ok := rb.(ResourceManagerV2); ok { - if _, ok := ret.resourceManagersV2[rType.Id]; ok { - return nil, fmt.Errorf("error: duplicate resource type found for resource managerV2 %s", rType.Id) - } - ret.resourceManagersV2[rType.Id] = resourceManager - // Support DeleteResourceV2 if connector implements both Create and Delete - if _, ok := ret.resourceDeletersV2[rType.Id]; ok { - // This should never happen - return nil, fmt.Errorf("error: duplicate resource type found for resource deleterV2 %s", rType.Id) - } - ret.resourceDeletersV2[rType.Id] = resourceManager - } else { - if resourceDeleter, ok := rb.(ResourceDeleterV2); ok { - if _, ok := ret.resourceDeletersV2[rType.Id]; ok { - return nil, fmt.Errorf("error: duplicate resource type found for resource deleterV2 %s", rType.Id) - } - ret.resourceDeletersV2[rType.Id] = resourceDeleter - } + if err := b.addProvisioner(ctx, rType.Id, rb); err != nil { + return nil, err } - if _, ok := rb.(OldAccountManager); ok { - return nil, fmt.Errorf("error: old account manager interface implemented for %s", rType.Id) + if err := b.addTargetedSyncer(ctx, rType.Id, rb); err != nil { + return nil, err } - if accountManager, ok := rb.(AccountManager); ok { - if ret.accountManager != nil { - return nil, fmt.Errorf("error: duplicate resource type found for account manager %s", rType.Id) - } - ret.accountManager = accountManager + if err := b.addResourceManager(ctx, rType.Id, rb); err != nil { + return nil, err } - if _, ok := rb.(OldCredentialManager); ok { - return nil, fmt.Errorf("error: old credential manager interface implemented for %s", rType.Id) + if err := b.addAccountManager(ctx, rType.Id, rb); err != nil { + return nil, err } - if credentialManagers, ok := rb.(CredentialManager); ok { - if _, ok := ret.credentialManagers[rType.Id]; ok { - return nil, fmt.Errorf("error: duplicate resource type found for credential manager %s", rType.Id) - } - ret.credentialManagers[rType.Id] = credentialManagers + if err := b.addCredentialManager(ctx, rType.Id, rb); err != nil { + return nil, err } } - return ret, nil + return b, nil case types.ConnectorServer: return c, nil @@ -689,10 +142,10 @@ func NewConnector(ctx context.Context, in interface{}, opts ...Opt) (types.Conne } } -type Opt func(b *builderImpl) error +type Opt func(b *builder) error func WithTicketingEnabled() Opt { - return func(b *builderImpl) error { + return func(b *builder) error { if _, ok := b.cb.(TicketManager); ok { b.ticketingEnabled = true return nil @@ -702,13 +155,13 @@ func WithTicketingEnabled() Opt { } func WithMetricsHandler(h metrics.Handler) Opt { - return func(b *builderImpl) error { + return func(b *builder) error { b.m = metrics.New(h) return nil } } -func (b *builderImpl) options(opts ...Opt) error { +func (b *builder) options(opts ...Opt) error { for _, opt := range opts { if err := opt(b); err != nil { return err @@ -718,270 +171,87 @@ func (b *builderImpl) options(opts ...Opt) error { return nil } -func validateProvisionerVersion(ctx context.Context, p ResourceSyncer) error { - _, ok := p.(ResourceProvisioner) - _, okV2 := p.(ResourceProvisionerV2) - - if ok && okV2 { - return fmt.Errorf("error: resource type %s implements both ResourceProvisioner and ResourceProvisionerV2", p.ResourceType(ctx).Id) - } - return nil -} - -// ListResourceTypes lists all available resource types. -func (b *builderImpl) ListResourceTypes( - ctx context.Context, - request *v2.ResourceTypesServiceListResourceTypesRequest, -) (*v2.ResourceTypesServiceListResourceTypesResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.ListResourceTypes") +// GetMetadata gets all metadata for a connector. +func (b *builder) GetMetadata(ctx context.Context, request *v2.ConnectorServiceGetMetadataRequest) (*v2.ConnectorServiceGetMetadataResponse, error) { + ctx, span := tracer.Start(ctx, "builder.GetMetadata") defer span.End() start := b.nowFunc() - tt := tasks.ListResourceTypesType - var out []*v2.ResourceType - - if len(b.resourceBuilders) == 0 { + tt := tasks.GetMetadataType + md, err := b.cb.Metadata(ctx) + if err != nil { b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: no resource builders found") + return nil, err } - for _, rb := range b.resourceBuilders { - out = append(out, rb.ResourceType(ctx)) + md.Capabilities, err = getCapabilities(ctx, b) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, err } - if len(out) == 0 { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: no resource types found") + annos := annotations.Annotations(md.Annotations) + if b.ticketManager != nil { + annos.Append(&v2.ExternalTicketSettings{Enabled: b.ticketingEnabled}) } + md.Annotations = annos b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.ResourceTypesServiceListResourceTypesResponse{List: out}, nil + return &v2.ConnectorServiceGetMetadataResponse{Metadata: md}, nil } -// ListResources returns all available resources for a given resource type ID. -func (b *builderImpl) ListResources(ctx context.Context, request *v2.ResourcesServiceListResourcesRequest) (*v2.ResourcesServiceListResourcesResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.ListResources") +// Validate validates the connector. +func (b *builder) Validate(ctx context.Context, request *v2.ConnectorServiceValidateRequest) (*v2.ConnectorServiceValidateResponse, error) { + ctx, span := tracer.Start(ctx, "builder.Validate") defer span.End() - start := b.nowFunc() - tt := tasks.ListResourcesType - rb, ok := b.resourceBuilders[request.ResourceTypeId] - if !ok { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: list resources with unknown resource type %s", request.ResourceTypeId) - } - out, nextPageToken, annos, err := rb.List(ctx, request.ParentResourceId, &pagination.Token{ - Size: int(request.PageSize), - Token: request.PageToken, + retryer := retry.NewRetryer(ctx, retry.RetryConfig{ + MaxAttempts: 5, + InitialDelay: 1 * time.Second, + MaxDelay: 0, }) - resp := &v2.ResourcesServiceListResourcesResponse{ - List: out, - NextPageToken: nextPageToken, - Annotations: annos, - } - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return resp, fmt.Errorf("error: listing resources failed: %w", err) - } - if request.PageToken != "" && request.PageToken == nextPageToken { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return resp, fmt.Errorf("error: listing resources failed: next page token is the same as the current page token. this is most likely a connector bug") - } - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return resp, nil -} + for { + annos, err := b.cb.Validate(ctx) + if err == nil { + return &v2.ConnectorServiceValidateResponse{ + Annotations: annos, + SdkVersion: sdk.Version, + }, nil + } -func (b *builderImpl) GetResource(ctx context.Context, request *v2.ResourceGetterServiceGetResourceRequest) (*v2.ResourceGetterServiceGetResourceResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.GetResource") - defer span.End() + if retryer.ShouldWaitAndRetry(ctx, err) { + continue + } - start := b.nowFunc() - tt := tasks.GetResourceType - resourceType := request.GetResourceId().GetResourceType() - rb, ok := b.resourceTargetedSyncers[resourceType] - if !ok { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, status.Errorf(codes.Unimplemented, "error: get resource with unknown resource type %s", resourceType) - } - - resource, annos, err := rb.Get(ctx, request.GetResourceId(), request.GetParentResourceId()) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: get resource failed: %w", err) - } - if resource == nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, status.Error(codes.NotFound, "error: get resource returned nil") - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.ResourceGetterServiceGetResourceResponse{ - Resource: resource, - Annotations: annos, - }, nil -} - -// ListEntitlements returns all the entitlements for a given resource. -func (b *builderImpl) ListEntitlements(ctx context.Context, request *v2.EntitlementsServiceListEntitlementsRequest) (*v2.EntitlementsServiceListEntitlementsResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.ListEntitlements") - defer span.End() - - start := b.nowFunc() - tt := tasks.ListEntitlementsType - rb, ok := b.resourceBuilders[request.Resource.Id.ResourceType] - if !ok { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: list entitlements with unknown resource type %s", request.Resource.Id.ResourceType) - } - - out, nextPageToken, annos, err := rb.Entitlements(ctx, request.Resource, &pagination.Token{ - Size: int(request.PageSize), - Token: request.PageToken, - }) - resp := &v2.EntitlementsServiceListEntitlementsResponse{ - List: out, - NextPageToken: nextPageToken, - Annotations: annos, - } - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return resp, fmt.Errorf("error: listing entitlements failed: %w", err) - } - if request.PageToken != "" && request.PageToken == nextPageToken { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return resp, fmt.Errorf("error: listing entitlements failed: next page token is the same as the current page token. this is most likely a connector bug") - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return resp, nil -} - -// ListGrants lists all the grants for a given resource. -func (b *builderImpl) ListGrants(ctx context.Context, request *v2.GrantsServiceListGrantsRequest) (*v2.GrantsServiceListGrantsResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.ListGrants") - defer span.End() - - start := b.nowFunc() - tt := tasks.ListGrantsType - rid := request.Resource.Id - rb, ok := b.resourceBuilders[rid.ResourceType] - if !ok { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: list entitlements with unknown resource type %s", rid.ResourceType) - } - - out, nextPageToken, annos, err := rb.Grants(ctx, request.Resource, &pagination.Token{ - Size: int(request.PageSize), - Token: request.PageToken, - }) - - resp := &v2.GrantsServiceListGrantsResponse{ - List: out, - NextPageToken: nextPageToken, - Annotations: annos, - } - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return resp, fmt.Errorf("error: listing grants for resource %s/%s failed: %w", rid.ResourceType, rid.Resource, err) - } - if request.PageToken != "" && request.PageToken == nextPageToken { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return resp, fmt.Errorf("error: listing grants for resource %s/%s failed: next page token is the same as the current page token. this is most likely a connector bug", - rid.ResourceType, - rid.Resource) - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return resp, nil -} - -// GetMetadata gets all metadata for a connector. -func (b *builderImpl) GetMetadata(ctx context.Context, request *v2.ConnectorServiceGetMetadataRequest) (*v2.ConnectorServiceGetMetadataResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.GetMetadata") - defer span.End() - - start := b.nowFunc() - tt := tasks.GetMetadataType - md, err := b.cb.Metadata(ctx) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, err - } - - md.Capabilities, err = getCapabilities(ctx, b) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, err - } - - annos := annotations.Annotations(md.Annotations) - if b.ticketManager != nil { - annos.Append(&v2.ExternalTicketSettings{Enabled: b.ticketingEnabled}) - } - md.Annotations = annos - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.ConnectorServiceGetMetadataResponse{Metadata: md}, nil -} - -func validateCapabilityDetails(ctx context.Context, credDetails *v2.CredentialDetails) error { - if credDetails.CapabilityAccountProvisioning != nil { - // Ensure that the preferred option is included and is part of the supported options - if credDetails.CapabilityAccountProvisioning.PreferredCredentialOption == v2.CapabilityDetailCredentialOption_CAPABILITY_DETAIL_CREDENTIAL_OPTION_UNSPECIFIED { - return status.Error(codes.InvalidArgument, "error: preferred credential creation option is not set") - } - if !slices.Contains(credDetails.CapabilityAccountProvisioning.SupportedCredentialOptions, credDetails.CapabilityAccountProvisioning.PreferredCredentialOption) { - return status.Error(codes.InvalidArgument, "error: preferred credential creation option is not part of the supported options") - } - } - - if credDetails.CapabilityCredentialRotation != nil { - // Ensure that the preferred option is included and is part of the supported options - if credDetails.CapabilityCredentialRotation.PreferredCredentialOption == v2.CapabilityDetailCredentialOption_CAPABILITY_DETAIL_CREDENTIAL_OPTION_UNSPECIFIED { - return status.Error(codes.InvalidArgument, "error: preferred credential rotation option is not set") - } - if !slices.Contains(credDetails.CapabilityCredentialRotation.SupportedCredentialOptions, credDetails.CapabilityCredentialRotation.PreferredCredentialOption) { - return status.Error(codes.InvalidArgument, "error: preferred credential rotation option is not part of the supported options") - } + return nil, fmt.Errorf("validate failed: %w", err) } - - return nil } -func getCredentialDetails(ctx context.Context, b *builderImpl) (*v2.CredentialDetails, error) { +func (b *builder) Cleanup(ctx context.Context, request *v2.ConnectorServiceCleanupRequest) (*v2.ConnectorServiceCleanupResponse, error) { l := ctxzap.Extract(ctx) - rv := &v2.CredentialDetails{} - - for _, rb := range b.resourceBuilders { - if am, ok := rb.(AccountManager); ok { - accountProvisioningCapabilityDetails, _, err := am.CreateAccountCapabilityDetails(ctx) - if err != nil { - l.Error("error: getting account provisioning details", zap.Error(err)) - return nil, fmt.Errorf("error: getting account provisioning details: %w", err) - } - rv.CapabilityAccountProvisioning = accountProvisioningCapabilityDetails - } - if cm, ok := rb.(CredentialManager); ok { - credentialRotationCapabilityDetails, _, err := cm.RotateCapabilityDetails(ctx) - if err != nil { - l.Error("error: getting credential management details", zap.Error(err)) - return nil, fmt.Errorf("error: getting credential management details: %w", err) - } - rv.CapabilityCredentialRotation = credentialRotationCapabilityDetails + // Clear session cache if available in context + sessionCache, err := session.GetSession(ctx) + if err != nil { + l.Warn("error getting session cache", zap.Error(err)) + } else if request.GetActiveSyncId() != "" { + err = sessionCache.Clear(ctx, session.WithSyncID(request.GetActiveSyncId())) + if err != nil { + l.Warn("error clearing session cache", zap.Error(err)) } } - - err := validateCapabilityDetails(ctx, rv) + // Clear all http caches at the end of a sync. This must be run in the child process, which is why it's in this function and not in syncer.go + err = uhttp.ClearCaches(ctx) if err != nil { - return nil, fmt.Errorf("error: validating capability details: %w", err) + l.Warn("error clearing http caches", zap.Error(err)) } - return rv, nil + resp := &v2.ConnectorServiceCleanupResponse{} + return resp, err } // getCapabilities gets all capabilities for a connector. -func getCapabilities(ctx context.Context, b *builderImpl) (*v2.ConnectorCapabilities, error) { +func getCapabilities(ctx context.Context, b *builder) (*v2.ConnectorCapabilities, error) { connectorCaps := make(map[v2.Capability]struct{}) resourceTypeCapabilities := []*v2.ResourceTypeCapability{} for _, rb := range b.resourceBuilders { @@ -1066,570 +336,57 @@ func getCapabilities(ctx context.Context, b *builderImpl) (*v2.ConnectorCapabili }, nil } -// Validate validates the connector. -func (b *builderImpl) Validate(ctx context.Context, request *v2.ConnectorServiceValidateRequest) (*v2.ConnectorServiceValidateResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.Validate") - defer span.End() - - retryer := retry.NewRetryer(ctx, retry.RetryConfig{ - MaxAttempts: 5, - InitialDelay: 1 * time.Second, - MaxDelay: 0, - }) - - for { - annos, err := b.cb.Validate(ctx) - if err == nil { - return &v2.ConnectorServiceValidateResponse{ - Annotations: annos, - SdkVersion: sdk.Version, - }, nil - } - - if retryer.ShouldWaitAndRetry(ctx, err) { - continue - } - - return nil, fmt.Errorf("validate failed: %w", err) - } -} - -func (b *builderImpl) Grant(ctx context.Context, request *v2.GrantManagerServiceGrantRequest) (*v2.GrantManagerServiceGrantResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.Grant") - defer span.End() - - start := b.nowFunc() - tt := tasks.GrantType - l := ctxzap.Extract(ctx) - - rt := request.Entitlement.Resource.Id.ResourceType - - retryer := retry.NewRetryer(ctx, retry.RetryConfig{ - MaxAttempts: 3, - InitialDelay: 15 * time.Second, - MaxDelay: 60 * time.Second, - }) - - var grantFunc func(ctx context.Context, principal *v2.Resource, entitlement *v2.Entitlement) ([]*v2.Grant, annotations.Annotations, error) - provisioner, ok := b.resourceProvisioners[rt] - if ok { - grantFunc = func(ctx context.Context, principal *v2.Resource, entitlement *v2.Entitlement) ([]*v2.Grant, annotations.Annotations, error) { - annos, err := provisioner.Grant(ctx, principal, entitlement) - if err != nil { - return nil, annos, err - } - return nil, annos, nil - } - } - provisionerV2, ok := b.resourceProvisionersV2[rt] - if ok { - grantFunc = provisionerV2.Grant - } - - if grantFunc == nil { - l.Error("error: resource type does not have provisioner configured", zap.String("resource_type", rt)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: resource type does not have provisioner configured") - } - - for { - grants, annos, err := grantFunc(ctx, request.Principal, request.Entitlement) - if err == nil { - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.GrantManagerServiceGrantResponse{Annotations: annos, Grants: grants}, nil +func validateCapabilityDetails(_ context.Context, credDetails *v2.CredentialDetails) error { + if credDetails.CapabilityAccountProvisioning != nil { + // Ensure that the preferred option is included and is part of the supported options + if credDetails.CapabilityAccountProvisioning.PreferredCredentialOption == v2.CapabilityDetailCredentialOption_CAPABILITY_DETAIL_CREDENTIAL_OPTION_UNSPECIFIED { + return status.Error(codes.InvalidArgument, "error: preferred credential creation option is not set") } - if retryer.ShouldWaitAndRetry(ctx, err) { - continue + if !slices.Contains(credDetails.CapabilityAccountProvisioning.SupportedCredentialOptions, credDetails.CapabilityAccountProvisioning.PreferredCredentialOption) { + return status.Error(codes.InvalidArgument, "error: preferred credential creation option is not part of the supported options") } - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("grant failed: %w", err) } -} - -func (b *builderImpl) Revoke(ctx context.Context, request *v2.GrantManagerServiceRevokeRequest) (*v2.GrantManagerServiceRevokeResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.Revoke") - defer span.End() - start := b.nowFunc() - tt := tasks.RevokeType - - l := ctxzap.Extract(ctx) - - rt := request.Grant.Entitlement.Resource.Id.ResourceType - - retryer := retry.NewRetryer(ctx, retry.RetryConfig{ - MaxAttempts: 3, - InitialDelay: 15 * time.Second, - MaxDelay: 60 * time.Second, - }) - - var revokeFunc func(ctx context.Context, grant *v2.Grant) (annotations.Annotations, error) - provisioner, ok := b.resourceProvisioners[rt] - if ok { - revokeFunc = provisioner.Revoke - } - provisionerV2, ok := b.resourceProvisionersV2[rt] - if ok { - revokeFunc = provisionerV2.Revoke - } - - if revokeFunc == nil { - l.Error("error: resource type does not have provisioner configured", zap.String("resource_type", rt)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: resource type does not have provisioner configured") - } - - for { - annos, err := revokeFunc(ctx, request.Grant) - if err == nil { - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.GrantManagerServiceRevokeResponse{Annotations: annos}, nil + if credDetails.CapabilityCredentialRotation != nil { + // Ensure that the preferred option is included and is part of the supported options + if credDetails.CapabilityCredentialRotation.PreferredCredentialOption == v2.CapabilityDetailCredentialOption_CAPABILITY_DETAIL_CREDENTIAL_OPTION_UNSPECIFIED { + return status.Error(codes.InvalidArgument, "error: preferred credential rotation option is not set") } - if retryer.ShouldWaitAndRetry(ctx, err) { - continue + if !slices.Contains(credDetails.CapabilityCredentialRotation.SupportedCredentialOptions, credDetails.CapabilityCredentialRotation.PreferredCredentialOption) { + return status.Error(codes.InvalidArgument, "error: preferred credential rotation option is not part of the supported options") } - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("revoke failed: %w", err) } -} - -// GetAsset streams the asset to the client. -// FIXME(jirwin): Asset streaming is disabled. -func (b *builderImpl) GetAsset(request *v2.AssetServiceGetAssetRequest, server v2.AssetService_GetAssetServer) error { - _, span := tracer.Start(server.Context(), "builderImpl.GetAsset") - defer span.End() return nil } -func (b *builderImpl) ListEventFeeds(ctx context.Context, request *v2.ListEventFeedsRequest) (*v2.ListEventFeedsResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.ListEventFeeds") - defer span.End() - - start := b.nowFunc() - tt := tasks.ListEventFeedsType - - feeds := make([]*v2.EventFeedMetadata, 0, len(b.eventFeeds)) - - for _, feed := range b.eventFeeds { - feeds = append(feeds, feed.EventFeedMetadata(ctx)) - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.ListEventFeedsResponse{ - List: feeds, - }, nil -} - -func (b *builderImpl) ListEvents(ctx context.Context, request *v2.ListEventsRequest) (*v2.ListEventsResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.ListEvents") - defer span.End() - - start := b.nowFunc() - feedId := request.GetEventFeedId() - - // If no feedId is provided, use the legacy Baton feed Id - if feedId == "" { - feedId = LegacyBatonFeedId - } - - feed, ok := b.eventFeeds[feedId] - if !ok { - return nil, status.Errorf(codes.NotFound, "error: event feed not found") - } - - tt := tasks.ListEventsType - events, streamState, annotations, err := feed.ListEvents(ctx, request.StartAt, &pagination.StreamToken{ - Size: int(request.PageSize), - Cursor: request.Cursor, - }) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: listing events failed: %w", err) - } - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.ListEventsResponse{ - Events: events, - Cursor: streamState.Cursor, - HasMore: streamState.HasMore, - Annotations: annotations, - }, nil -} - -func (b *builderImpl) CreateResource(ctx context.Context, request *v2.CreateResourceRequest) (*v2.CreateResourceResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.CreateResource") - defer span.End() - - start := b.nowFunc() - tt := tasks.CreateResourceType - l := ctxzap.Extract(ctx) - rt := request.GetResource().GetId().GetResourceType() - - var manager interface { - Create(ctx context.Context, resource *v2.Resource) (*v2.Resource, annotations.Annotations, error) - } - - manager, ok := b.resourceManagersV2[rt] - if !ok { - manager, ok = b.resourceManagers[rt] - } - - if ok { - resource, annos, err := manager.Create(ctx, request.Resource) - if err != nil { - l.Error("error: create resource failed", zap.Error(err)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: create resource failed: %w", err) - } - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.CreateResourceResponse{Created: resource, Annotations: annos}, nil - } - l.Error("error: resource type does not have resource Create() configured", zap.String("resource_type", rt)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, status.Error(codes.Unimplemented, fmt.Sprintf("resource type %s does not have resource Create() configured", rt)) -} - -func (b *builderImpl) DeleteResource(ctx context.Context, request *v2.DeleteResourceRequest) (*v2.DeleteResourceResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.DeleteResource") - defer span.End() - - start := b.nowFunc() - tt := tasks.DeleteResourceType - - l := ctxzap.Extract(ctx) - rt := request.GetResourceId().GetResourceType() - var rsDeleter ResourceDeleter - var rsDeleterV2 ResourceDeleterV2 - var ok bool - - rsDeleterV2, ok = b.resourceManagersV2[rt] - if !ok { - rsDeleterV2, ok = b.resourceDeletersV2[rt] - } - - if ok { - annos, err := rsDeleterV2.Delete(ctx, request.ResourceId, request.ParentResourceId) - if err != nil { - l.Error("error: deleteV2 resource failed", zap.Error(err)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: delete resource failed: %w", err) - } - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.DeleteResourceResponse{Annotations: annos}, nil - } - - rsDeleter, ok = b.resourceManagers[rt] - if !ok { - rsDeleter, ok = b.resourceDeleters[rt] - } - if ok { - annos, err := rsDeleter.Delete(ctx, request.GetResourceId()) - if err != nil { - l.Error("error: delete resource failed", zap.Error(err)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: delete resource failed: %w", err) - } - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.DeleteResourceResponse{Annotations: annos}, nil - } - l.Error("error: resource type does not have resource Delete() configured", zap.String("resource_type", rt)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, status.Error(codes.Unimplemented, fmt.Sprintf("resource type %s does not have resource Delete() configured", rt)) -} - -func (b *builderImpl) DeleteResourceV2(ctx context.Context, request *v2.DeleteResourceV2Request) (*v2.DeleteResourceV2Response, error) { - ctx, span := tracer.Start(ctx, "builderImpl.DeleteResourceV2") - defer span.End() - - start := b.nowFunc() - tt := tasks.DeleteResourceType - - l := ctxzap.Extract(ctx) - rt := request.GetResourceId().GetResourceType() - var rsDeleter ResourceDeleter - var rsDeleterV2 ResourceDeleterV2 - var ok bool - - rsDeleterV2, ok = b.resourceManagersV2[rt] - if !ok { - rsDeleterV2, ok = b.resourceDeletersV2[rt] - } - - if ok { - annos, err := rsDeleterV2.Delete(ctx, request.ResourceId, request.ParentResourceId) - if err != nil { - l.Error("error: deleteV2 resource failed", zap.Error(err)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: delete resource failed: %w", err) - } - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.DeleteResourceV2Response{Annotations: annos}, nil - } - - rsDeleter, ok = b.resourceManagers[rt] - if !ok { - rsDeleter, ok = b.resourceDeleters[rt] - } - if ok { - annos, err := rsDeleter.Delete(ctx, request.GetResourceId()) - if err != nil { - l.Error("error: delete resource failed", zap.Error(err)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: delete resource failed: %w", err) - } - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.DeleteResourceV2Response{Annotations: annos}, nil - } - l.Error("error: resource type does not have resource Delete() configured", zap.String("resource_type", rt)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, status.Error(codes.Unimplemented, fmt.Sprintf("resource type %s does not have resource Delete() configured", rt)) -} - -func (b *builderImpl) RotateCredential(ctx context.Context, request *v2.RotateCredentialRequest) (*v2.RotateCredentialResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.RotateCredential") - defer span.End() - - start := b.nowFunc() - tt := tasks.RotateCredentialsType - l := ctxzap.Extract(ctx) - rt := request.GetResourceId().GetResourceType() - manager, ok := b.credentialManagers[rt] - if !ok { - l.Error("error: resource type does not have credential manager configured", zap.String("resource_type", rt)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, status.Error(codes.Unimplemented, "resource type does not have credential manager configured") - } - - opts, err := crypto.ConvertCredentialOptions(ctx, b.clientSecret, request.GetCredentialOptions(), request.GetEncryptionConfigs()) - if err != nil { - l.Error("error: converting credential options failed", zap.Error(err)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: converting credential options failed: %w", err) - } - - plaintexts, annos, err := manager.Rotate(ctx, request.GetResourceId(), opts) - if err != nil { - l.Error("error: rotate credentials on resource failed", zap.Error(err)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: rotate credentials on resource failed: %w", err) - } - - pkem, err := crypto.NewEncryptionManager(request.GetCredentialOptions(), request.GetEncryptionConfigs()) - if err != nil { - l.Error("error: creating encryption manager failed", zap.Error(err)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: creating encryption manager failed: %w", err) - } - - var encryptedDatas []*v2.EncryptedData - for _, plaintextCredential := range plaintexts { - encryptedData, err := pkem.Encrypt(ctx, plaintextCredential) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, err - } - encryptedDatas = append(encryptedDatas, encryptedData...) - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return &v2.RotateCredentialResponse{ - Annotations: annos, - ResourceId: request.GetResourceId(), - EncryptedData: encryptedDatas, - }, nil -} - -func (b *builderImpl) Cleanup(ctx context.Context, request *v2.ConnectorServiceCleanupRequest) (*v2.ConnectorServiceCleanupResponse, error) { +func getCredentialDetails(ctx context.Context, b *builder) (*v2.CredentialDetails, error) { l := ctxzap.Extract(ctx) + rv := &v2.CredentialDetails{} - // Clear session cache if available in context - sessionCache, err := session.GetSession(ctx) - if err != nil { - l.Warn("error getting session cache", zap.Error(err)) - } else if request.GetActiveSyncId() != "" { - err = sessionCache.Clear(ctx, session.WithSyncID(request.GetActiveSyncId())) - if err != nil { - l.Warn("error clearing session cache", zap.Error(err)) + for _, rb := range b.resourceBuilders { + if am, ok := rb.(AccountManager); ok { + accountProvisioningCapabilityDetails, _, err := am.CreateAccountCapabilityDetails(ctx) + if err != nil { + l.Error("error: getting account provisioning details", zap.Error(err)) + return nil, fmt.Errorf("error: getting account provisioning details: %w", err) + } + rv.CapabilityAccountProvisioning = accountProvisioningCapabilityDetails } - } - // Clear all http caches at the end of a sync. This must be run in the child process, which is why it's in this function and not in syncer.go - err = uhttp.ClearCaches(ctx) - if err != nil { - l.Warn("error clearing http caches", zap.Error(err)) - } - resp := &v2.ConnectorServiceCleanupResponse{} - return resp, err -} - -func (b *builderImpl) CreateAccount(ctx context.Context, request *v2.CreateAccountRequest) (*v2.CreateAccountResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.CreateAccount") - defer span.End() - - start := b.nowFunc() - tt := tasks.CreateAccountType - l := ctxzap.Extract(ctx) - if b.accountManager == nil { - l.Error("error: connector does not have account manager configured") - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, status.Error(codes.Unimplemented, "connector does not have credential manager configured") - } - - opts, err := crypto.ConvertCredentialOptions(ctx, b.clientSecret, request.GetCredentialOptions(), request.GetEncryptionConfigs()) - if err != nil { - l.Error("error: converting credential options failed", zap.Error(err)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: converting credential options failed: %w", err) - } - - result, plaintexts, annos, err := b.accountManager.CreateAccount(ctx, request.GetAccountInfo(), opts) - if err != nil { - l.Error("error: create account failed", zap.Error(err)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: create account failed: %w", err) - } - pkem, err := crypto.NewEncryptionManager(request.GetCredentialOptions(), request.GetEncryptionConfigs()) - if err != nil { - l.Error("error: creating encryption manager failed", zap.Error(err)) - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: creating encryption manager failed: %w", err) - } - - var encryptedDatas []*v2.EncryptedData - for _, plaintextCredential := range plaintexts { - encryptedData, err := pkem.Encrypt(ctx, plaintextCredential) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, err + if cm, ok := rb.(CredentialManager); ok { + credentialRotationCapabilityDetails, _, err := cm.RotateCapabilityDetails(ctx) + if err != nil { + l.Error("error: getting credential management details", zap.Error(err)) + return nil, fmt.Errorf("error: getting credential management details: %w", err) + } + rv.CapabilityCredentialRotation = credentialRotationCapabilityDetails } - encryptedDatas = append(encryptedDatas, encryptedData...) - } - - rv := &v2.CreateAccountResponse{ - EncryptedData: encryptedDatas, - Annotations: annos, } - switch r := result.(type) { - case *v2.CreateAccountResponse_SuccessResult: - rv.Result = &v2.CreateAccountResponse_Success{Success: r} - case *v2.CreateAccountResponse_ActionRequiredResult: - rv.Result = &v2.CreateAccountResponse_ActionRequired{ActionRequired: r} - default: - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, status.Error(codes.Unimplemented, fmt.Sprintf("unknown result type: %T", result)) - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return rv, nil -} - -func (b *builderImpl) ListActionSchemas(ctx context.Context, request *v2.ListActionSchemasRequest) (*v2.ListActionSchemasResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.ListActionSchemas") - defer span.End() - - start := b.nowFunc() - tt := tasks.ActionListSchemasType - if b.actionManager == nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: action manager not implemented") - } - - actionSchemas, annos, err := b.actionManager.ListActionSchemas(ctx) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: listing action schemas failed: %w", err) - } - - rv := &v2.ListActionSchemasResponse{ - Schemas: actionSchemas, - Annotations: annos, - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return rv, nil -} - -func (b *builderImpl) GetActionSchema(ctx context.Context, request *v2.GetActionSchemaRequest) (*v2.GetActionSchemaResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.GetActionSchema") - defer span.End() - - start := b.nowFunc() - tt := tasks.ActionGetSchemaType - if b.actionManager == nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: action manager not implemented") - } - - actionSchema, annos, err := b.actionManager.GetActionSchema(ctx, request.GetName()) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: getting action schema failed: %w", err) - } - - rv := &v2.GetActionSchemaResponse{ - Schema: actionSchema, - Annotations: annos, - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return rv, nil -} - -func (b *builderImpl) InvokeAction(ctx context.Context, request *v2.InvokeActionRequest) (*v2.InvokeActionResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.InvokeAction") - defer span.End() - - start := b.nowFunc() - tt := tasks.ActionInvokeType - if b.actionManager == nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: action manager not implemented") - } - - id, status, resp, annos, err := b.actionManager.InvokeAction(ctx, request.GetName(), request.GetArgs()) + err := validateCapabilityDetails(ctx, rv) if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: invoking action failed: %w", err) - } - - rv := &v2.InvokeActionResponse{ - Id: id, - Name: request.GetName(), - Status: status, - Annotations: annos, - Response: resp, + return nil, fmt.Errorf("error: validating capability details: %w", err) } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) return rv, nil } - -func (b *builderImpl) GetActionStatus(ctx context.Context, request *v2.GetActionStatusRequest) (*v2.GetActionStatusResponse, error) { - ctx, span := tracer.Start(ctx, "builderImpl.GetActionStatus") - defer span.End() - - start := b.nowFunc() - tt := tasks.ActionStatusType - if b.actionManager == nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: action manager not implemented") - } - - status, name, rv, annos, err := b.actionManager.GetActionStatus(ctx, request.GetId()) - if err != nil { - b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) - return nil, fmt.Errorf("error: getting action status failed: %w", err) - } - - resp := &v2.GetActionStatusResponse{ - Id: request.GetId(), - Name: name, - Status: status, - Annotations: annos, - Response: rv, - } - - b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) - return resp, nil -} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/credentials.go b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/credentials.go new file mode 100644 index 00000000..aa96970f --- /dev/null +++ b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/credentials.go @@ -0,0 +1,101 @@ +package connectorbuilder + +import ( + "context" + "fmt" + + v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" + "github.com/conductorone/baton-sdk/pkg/annotations" + "github.com/conductorone/baton-sdk/pkg/crypto" + "github.com/conductorone/baton-sdk/pkg/types/tasks" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// CredentialManager extends ResourceSyncer to add capabilities for managing credentials. +// Implementing this interface indicates the connector supports rotating credentials +// for resources of the associated type. This is commonly used for user accounts +// or service accounts that have rotatable credentials. +type CredentialManager interface { + ResourceSyncer + Rotate(ctx context.Context, + resourceId *v2.ResourceId, + credentialOptions *v2.LocalCredentialOptions) ([]*v2.PlaintextData, annotations.Annotations, error) + RotateCapabilityDetails(ctx context.Context) (*v2.CredentialDetailsCredentialRotation, annotations.Annotations, error) +} + +type OldCredentialManager interface { + Rotate(ctx context.Context, + resourceId *v2.ResourceId, + credentialOptions *v2.CredentialOptions) ([]*v2.PlaintextData, annotations.Annotations, error) +} + +func (b *builder) RotateCredential(ctx context.Context, request *v2.RotateCredentialRequest) (*v2.RotateCredentialResponse, error) { + ctx, span := tracer.Start(ctx, "builder.RotateCredential") + defer span.End() + + start := b.nowFunc() + tt := tasks.RotateCredentialsType + l := ctxzap.Extract(ctx) + rt := request.GetResourceId().GetResourceType() + manager, ok := b.credentialManagers[rt] + if !ok { + l.Error("error: resource type does not have credential manager configured", zap.String("resource_type", rt)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, status.Error(codes.Unimplemented, "resource type does not have credential manager configured") + } + + opts, err := crypto.ConvertCredentialOptions(ctx, b.clientSecret, request.GetCredentialOptions(), request.GetEncryptionConfigs()) + if err != nil { + l.Error("error: converting credential options failed", zap.Error(err)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: converting credential options failed: %w", err) + } + + plaintexts, annos, err := manager.Rotate(ctx, request.GetResourceId(), opts) + if err != nil { + l.Error("error: rotate credentials on resource failed", zap.Error(err)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: rotate credentials on resource failed: %w", err) + } + + pkem, err := crypto.NewEncryptionManager(request.GetCredentialOptions(), request.GetEncryptionConfigs()) + if err != nil { + l.Error("error: creating encryption manager failed", zap.Error(err)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: creating encryption manager failed: %w", err) + } + + var encryptedDatas []*v2.EncryptedData + for _, plaintextCredential := range plaintexts { + encryptedData, err := pkem.Encrypt(ctx, plaintextCredential) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, err + } + encryptedDatas = append(encryptedDatas, encryptedData...) + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.RotateCredentialResponse{ + Annotations: annos, + ResourceId: request.GetResourceId(), + EncryptedData: encryptedDatas, + }, nil +} + +func (b *builder) addCredentialManager(_ context.Context, typeId string, rb ResourceSyncer) error { + if _, ok := rb.(OldCredentialManager); ok { + return fmt.Errorf("error: old credential manager interface implemented for %s", typeId) + } + + if credentialManagers, ok := rb.(CredentialManager); ok { + if _, ok := b.credentialManagers[typeId]; ok { + return fmt.Errorf("error: duplicate resource type found for credential manager %s", typeId) + } + b.credentialManagers[typeId] = credentialManagers + } + return nil +} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/events.go b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/events.go new file mode 100644 index 00000000..715f0319 --- /dev/null +++ b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/events.go @@ -0,0 +1,155 @@ +package connectorbuilder + +import ( + "context" + "fmt" + + v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" + "github.com/conductorone/baton-sdk/pkg/annotations" + "github.com/conductorone/baton-sdk/pkg/pagination" + "github.com/conductorone/baton-sdk/pkg/types/tasks" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/timestamppb" +) + +const ( + LegacyBatonFeedId = "baton_feed_event" +) + +// Deprecated: This interface is deprecated in favor of EventProviderV2 which supports +// multiple event feeds. Implementing this interface indicates the connector can provide +// a single stream of events from the external system, enabling near real-time updates +// in Baton. New connectors should implement EventProviderV2 instead. +type EventProvider interface { + ConnectorBuilder + EventLister +} + +// NewEventProviderV2 is a new interface that allows connectors to provide multiple event feeds. +// +// This is the recommended interface for implementing event feed support in new connectors. +type EventProviderV2 interface { + ConnectorBuilder + EventFeeds(ctx context.Context) []EventFeed +} + +// EventFeed is a single stream of events from the external system. +// +// EventFeedMetadata describes this feed, and a connector can have multiple feeds. +type EventFeed interface { + EventLister + EventFeedMetadata(ctx context.Context) *v2.EventFeedMetadata +} + +// Compatibility interface lets us handle both EventFeed and EventProvider the same. +type EventLister interface { + ListEvents(ctx context.Context, earliestEvent *timestamppb.Timestamp, pToken *pagination.StreamToken) ([]*v2.Event, *pagination.StreamState, annotations.Annotations, error) +} + +func newEventFeedV1to2(eventFeed EventLister) EventFeed { + return &oldEventFeedWrapper{ + feed: eventFeed, + } +} + +type oldEventFeedWrapper struct { + feed EventLister +} + +func (e *oldEventFeedWrapper) EventFeedMetadata(ctx context.Context) *v2.EventFeedMetadata { + return &v2.EventFeedMetadata{ + Id: LegacyBatonFeedId, + SupportedEventTypes: []v2.EventType{v2.EventType_EVENT_TYPE_UNSPECIFIED}, + } +} + +func (e *oldEventFeedWrapper) ListEvents( + ctx context.Context, + earliestEvent *timestamppb.Timestamp, + pToken *pagination.StreamToken, +) ([]*v2.Event, *pagination.StreamState, annotations.Annotations, error) { + return e.feed.ListEvents(ctx, earliestEvent, pToken) +} + +func (b *builder) ListEventFeeds(ctx context.Context, request *v2.ListEventFeedsRequest) (*v2.ListEventFeedsResponse, error) { + ctx, span := tracer.Start(ctx, "builder.ListEventFeeds") + defer span.End() + + start := b.nowFunc() + tt := tasks.ListEventFeedsType + + feeds := make([]*v2.EventFeedMetadata, 0, len(b.eventFeeds)) + + for _, feed := range b.eventFeeds { + feeds = append(feeds, feed.EventFeedMetadata(ctx)) + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.ListEventFeedsResponse{ + List: feeds, + }, nil +} + +func (b *builder) ListEvents(ctx context.Context, request *v2.ListEventsRequest) (*v2.ListEventsResponse, error) { + ctx, span := tracer.Start(ctx, "builder.ListEvents") + defer span.End() + + start := b.nowFunc() + feedId := request.GetEventFeedId() + + // If no feedId is provided, use the legacy Baton feed Id + if feedId == "" { + feedId = LegacyBatonFeedId + } + + feed, ok := b.eventFeeds[feedId] + if !ok { + return nil, status.Errorf(codes.NotFound, "error: event feed not found") + } + + tt := tasks.ListEventsType + events, streamState, annotations, err := feed.ListEvents(ctx, request.StartAt, &pagination.StreamToken{ + Size: int(request.PageSize), + Cursor: request.Cursor, + }) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: listing events failed: %w", err) + } + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.ListEventsResponse{ + Events: events, + Cursor: streamState.Cursor, + HasMore: streamState.HasMore, + Annotations: annotations, + }, nil +} + +func (b *builder) addEventFeed(ctx context.Context, c ConnectorBuilder) error { + if ep, ok := c.(EventProviderV2); ok { + for _, ef := range ep.EventFeeds(ctx) { + feedData := ef.EventFeedMetadata(ctx) + if feedData == nil { + return fmt.Errorf("error: event feed metadata is nil") + } + if err := feedData.Validate(); err != nil { + return fmt.Errorf("error: event feed metadata for %s is invalid: %w", feedData.Id, err) + } + if _, ok := b.eventFeeds[feedData.Id]; ok { + return fmt.Errorf("error: duplicate event feed id found: %s", feedData.Id) + } + b.eventFeeds[feedData.Id] = ef + } + } + + if ep, ok := c.(EventProvider); ok { + // Register the legacy Baton feed as a v2 event feed + // implementing both v1 and v2 event feeds is not supported. + if len(b.eventFeeds) != 0 { + return fmt.Errorf("error: using legacy event feed is not supported when using EventProviderV2") + } + b.eventFeeds[LegacyBatonFeedId] = newEventFeedV1to2(ep) + } + return nil +} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/resource_manager.go b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/resource_manager.go new file mode 100644 index 00000000..d2c9f89d --- /dev/null +++ b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/resource_manager.go @@ -0,0 +1,213 @@ +package connectorbuilder + +import ( + "context" + "fmt" + + v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" + "github.com/conductorone/baton-sdk/pkg/annotations" + "github.com/conductorone/baton-sdk/pkg/types/tasks" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" + "go.uber.org/zap" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// ResourceManager extends ResourceSyncer to add capabilities for creating resources. +// +// Implementing this interface indicates the connector supports creating and deleting resources +// of the associated resource type. A ResourceManager automatically provides ResourceDeleter +// functionality. +type ResourceManager interface { + ResourceCreator + ResourceDeleter +} + +// ResourceManagerV2 extends ResourceSyncer to add capabilities for creating resources. +// +// This is the recommended interface for implementing resource creation operations in new connectors. +type ResourceManagerV2 interface { + ResourceCreator + ResourceDeleterV2 +} + +type ResourceCreator interface { + Create(ctx context.Context, resource *v2.Resource) (*v2.Resource, annotations.Annotations, error) +} + +// ResourceDeleter extends ResourceSyncer to add capabilities for deleting resources. +// +// Implementing this interface indicates the connector supports deleting resources +// of the associated resource type. +type ResourceDeleter interface { + ResourceSyncer + Delete(ctx context.Context, resourceId *v2.ResourceId) (annotations.Annotations, error) +} + +// ResourceDeleterV2 extends ResourceSyncer to add capabilities for deleting resources. +// +// This is the recommended interface for implementing resource deletion operations in new connectors. +// It differs from ResourceDeleter by having the resource, not just the id. +type ResourceDeleterV2 interface { + ResourceSyncer + Delete(ctx context.Context, resourceId *v2.ResourceId, parentResourceID *v2.ResourceId) (annotations.Annotations, error) +} + +func (b *builder) CreateResource(ctx context.Context, request *v2.CreateResourceRequest) (*v2.CreateResourceResponse, error) { + ctx, span := tracer.Start(ctx, "builder.CreateResource") + defer span.End() + + start := b.nowFunc() + tt := tasks.CreateResourceType + l := ctxzap.Extract(ctx) + rt := request.GetResource().GetId().GetResourceType() + + manager, ok := b.resourceManagers[rt] + if !ok { + l.Error("error: resource type does not have resource Create() configured", zap.String("resource_type", rt)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, status.Error(codes.Unimplemented, fmt.Sprintf("resource type %s does not have resource Create() configured", rt)) + } + resource, annos, err := manager.Create(ctx, request.Resource) + if err != nil { + l.Error("error: create resource failed", zap.Error(err)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: create resource failed: %w", err) + } + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.CreateResourceResponse{Created: resource, Annotations: annos}, nil +} + +func (b *builder) DeleteResource(ctx context.Context, request *v2.DeleteResourceRequest) (*v2.DeleteResourceResponse, error) { + ctx, span := tracer.Start(ctx, "builder.DeleteResource") + defer span.End() + + start := b.nowFunc() + tt := tasks.DeleteResourceType + + l := ctxzap.Extract(ctx) + rt := request.GetResourceId().GetResourceType() + var rsDeleter ResourceDeleterV2 + var ok bool + + rsDeleter, ok = b.resourceManagers[rt] + if !ok { + rsDeleter, ok = b.resourceDeleters[rt] + } + + if !ok { + l.Error("error: resource type does not have resource Delete() configured", zap.String("resource_type", rt)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, status.Error(codes.Unimplemented, fmt.Sprintf("resource type %s does not have resource Delete() configured", rt)) + } + + annos, err := rsDeleter.Delete(ctx, request.ResourceId, request.ParentResourceId) + if err != nil { + l.Error("error: deleteV2 resource failed", zap.Error(err)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: delete resource failed: %w", err) + } + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.DeleteResourceResponse{Annotations: annos}, nil +} + +func (b *builder) DeleteResourceV2(ctx context.Context, request *v2.DeleteResourceV2Request) (*v2.DeleteResourceV2Response, error) { + ctx, span := tracer.Start(ctx, "builder.DeleteResourceV2") + defer span.End() + + start := b.nowFunc() + tt := tasks.DeleteResourceType + + l := ctxzap.Extract(ctx) + rt := request.GetResourceId().GetResourceType() + var rsDeleter ResourceDeleterV2 + var ok bool + + rsDeleter, ok = b.resourceManagers[rt] + if !ok { + rsDeleter, ok = b.resourceDeleters[rt] + } + + if !ok { + l.Error("error: resource type does not have resource Delete() configured", zap.String("resource_type", rt)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, status.Error(codes.Unimplemented, fmt.Sprintf("resource type %s does not have resource Delete() configured", rt)) + } + + annos, err := rsDeleter.Delete(ctx, request.ResourceId, request.ParentResourceId) + if err != nil { + l.Error("error: deleteV2 resource failed", zap.Error(err)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: delete resource failed: %w", err) + } + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.DeleteResourceV2Response{Annotations: annos}, nil +} + +func newResourceManager1to2(resourceManager ResourceManager) ResourceManagerV2 { + return &resourceManager1to2{resourceManager} +} + +type resourceManager1to2 struct { + ResourceManager +} + +func (r *resourceManager1to2) Delete(ctx context.Context, resourceId *v2.ResourceId, parentResourceID *v2.ResourceId) (annotations.Annotations, error) { + return r.ResourceManager.Delete(ctx, resourceId) +} + +func newDeleter1to2(resourceDeleter ResourceDeleter) ResourceDeleterV2 { + return &deleter1to2{resourceDeleter} +} + +type deleter1to2 struct { + ResourceDeleter +} + +func (d *deleter1to2) Delete(ctx context.Context, resourceId *v2.ResourceId, parentResourceID *v2.ResourceId) (annotations.Annotations, error) { + // Just drop the parentResourceID... + return d.ResourceDeleter.Delete(ctx, resourceId) +} + +func (b *builder) addResourceManager(_ context.Context, typeId string, rb ResourceSyncer) error { + if resourceManager, ok := rb.(ResourceManager); ok { + if _, ok := b.resourceManagers[typeId]; ok { + return fmt.Errorf("error: duplicate resource type found for resource manager %s", typeId) + } + b.resourceManagers[typeId] = newResourceManager1to2(resourceManager) + // Support DeleteResourceV2 if connector implements both Create and Delete + if _, ok := b.resourceDeleters[typeId]; ok { + // This should never happen + return fmt.Errorf("error: duplicate resource type found for resource deleter %s", typeId) + } + b.resourceDeleters[typeId] = newDeleter1to2(resourceManager) + } else { + if resourceDeleter, ok := rb.(ResourceDeleter); ok { + if _, ok := b.resourceDeleters[typeId]; ok { + return fmt.Errorf("error: duplicate resource type found for resource deleter %s", typeId) + } + b.resourceDeleters[typeId] = newDeleter1to2(resourceDeleter) + } + } + + if resourceManager, ok := rb.(ResourceManagerV2); ok { + if _, ok := b.resourceManagers[typeId]; ok { + return fmt.Errorf("error: duplicate resource type found for resource managerV2 %s", typeId) + } + b.resourceManagers[typeId] = resourceManager + // Support DeleteResourceV2 if connector implements both Create and Delete + if _, ok := b.resourceDeleters[typeId]; ok { + // This should never happen + return fmt.Errorf("error: duplicate resource type found for resource deleterV2 %s", typeId) + } + b.resourceDeleters[typeId] = resourceManager + } else { + if resourceDeleter, ok := rb.(ResourceDeleterV2); ok { + if _, ok := b.resourceDeleters[typeId]; ok { + return fmt.Errorf("error: duplicate resource type found for resource deleterV2 %s", typeId) + } + b.resourceDeleters[typeId] = resourceDeleter + } + } + return nil +} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/resource_provisioner.go b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/resource_provisioner.go new file mode 100644 index 00000000..06e2f094 --- /dev/null +++ b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/resource_provisioner.go @@ -0,0 +1,167 @@ +package connectorbuilder + +import ( + "context" + "fmt" + "time" + + v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" + "github.com/conductorone/baton-sdk/pkg/annotations" + "github.com/conductorone/baton-sdk/pkg/retry" + "github.com/conductorone/baton-sdk/pkg/types/tasks" + "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" + "go.uber.org/zap" +) + +// ResourceProvisioner extends ResourceSyncer to add capabilities for granting and revoking access. +// +// Note: ResourceProvisionerV2 is preferred for new connectors as it provides +// enhanced grant capabilities. +// +// Implementing this interface indicates the connector supports provisioning operations +// for the associated resource type. + +type RevokeProvisioner interface { + Revoke(ctx context.Context, grant *v2.Grant) (annotations.Annotations, error) +} + +type ResourceProvisioner interface { + ResourceSyncer + RevokeProvisioner + ResourceType(ctx context.Context) *v2.ResourceType + Grant(ctx context.Context, resource *v2.Resource, entitlement *v2.Entitlement) (annotations.Annotations, error) +} + +// ResourceProvisionerV2 extends ResourceSyncer to add capabilities for granting and revoking access +// with enhanced functionality compared to ResourceProvisioner. +// +// This is the recommended interface for implementing provisioning operations in new connectors. +// It differs from ResourceProvisioner by returning a list of grants from the Grant method. +type ResourceProvisionerV2 interface { + ResourceSyncer + RevokeProvisioner + ResourceType(ctx context.Context) *v2.ResourceType + Grant(ctx context.Context, resource *v2.Resource, entitlement *v2.Entitlement) ([]*v2.Grant, annotations.Annotations, error) +} + +func (b *builder) Grant(ctx context.Context, request *v2.GrantManagerServiceGrantRequest) (*v2.GrantManagerServiceGrantResponse, error) { + ctx, span := tracer.Start(ctx, "builder.Grant") + defer span.End() + + start := b.nowFunc() + tt := tasks.GrantType + l := ctxzap.Extract(ctx) + + rt := request.Entitlement.Resource.Id.ResourceType + + provisioner, ok := b.resourceProvisioners[rt] + + if !ok { + l.Error("error: resource type does not have provisioner configured", zap.String("resource_type", rt)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: resource type does not have provisioner configured") + } + + retryer := retry.NewRetryer(ctx, retry.RetryConfig{ + MaxAttempts: 3, + InitialDelay: 15 * time.Second, + MaxDelay: 60 * time.Second, + }) + + for { + grants, annos, err := provisioner.Grant(ctx, request.Principal, request.Entitlement) + if err == nil { + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.GrantManagerServiceGrantResponse{Annotations: annos, Grants: grants}, nil + } + if retryer.ShouldWaitAndRetry(ctx, err) { + continue + } + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("grant failed: %w", err) + } +} + +func (b *builder) Revoke(ctx context.Context, request *v2.GrantManagerServiceRevokeRequest) (*v2.GrantManagerServiceRevokeResponse, error) { + ctx, span := tracer.Start(ctx, "builder.Revoke") + defer span.End() + + start := b.nowFunc() + tt := tasks.RevokeType + + l := ctxzap.Extract(ctx) + + rt := request.Grant.Entitlement.Resource.Id.ResourceType + + var revokeProvisioner RevokeProvisioner + provisioner, ok := b.resourceProvisioners[rt] + if ok { + revokeProvisioner = provisioner + } + + if revokeProvisioner == nil { + l.Error("error: resource type does not have provisioner configured", zap.String("resource_type", rt)) + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: resource type does not have provisioner configured") + } + + retryer := retry.NewRetryer(ctx, retry.RetryConfig{ + MaxAttempts: 3, + InitialDelay: 15 * time.Second, + MaxDelay: 60 * time.Second, + }) + + for { + annos, err := revokeProvisioner.Revoke(ctx, request.Grant) + if err == nil { + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.GrantManagerServiceRevokeResponse{Annotations: annos}, nil + } + if retryer.ShouldWaitAndRetry(ctx, err) { + continue + } + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("revoke failed: %w", err) + } +} + +func newResourceProvisionerV1to2(resourceProvisioner ResourceProvisioner) ResourceProvisionerV2 { + return &resourceProvisionerV1to2{ + ResourceProvisioner: resourceProvisioner, + } +} + +type resourceProvisionerV1to2 struct { + ResourceProvisioner +} + +func (r *resourceProvisionerV1to2) Grant(ctx context.Context, resource *v2.Resource, entitlement *v2.Entitlement) ([]*v2.Grant, annotations.Annotations, error) { + annos, err := r.ResourceProvisioner.Grant(ctx, resource, entitlement) + if err != nil { + return nil, annos, err + } + return nil, annos, nil +} + +func (b *builder) addProvisioner(_ context.Context, typeId string, rb ResourceSyncer) error { + _, hasV1 := rb.(ResourceProvisioner) + _, hasV2 := rb.(ResourceProvisionerV2) + + if hasV1 && hasV2 { + return fmt.Errorf("error: resource type %s implements both ResourceProvisioner and ResourceProvisionerV2", typeId) + } + + if provisioner, ok := rb.(ResourceProvisioner); ok { + if _, ok := b.resourceProvisioners[typeId]; ok { + return fmt.Errorf("error: duplicate resource type found for resource provisioner %s", typeId) + } + b.resourceProvisioners[typeId] = newResourceProvisionerV1to2(provisioner) + } + if provisioner, ok := rb.(ResourceProvisionerV2); ok { + if _, ok := b.resourceProvisioners[typeId]; ok { + return fmt.Errorf("error: duplicate resource type found for resource provisioner v2 %s", typeId) + } + b.resourceProvisioners[typeId] = provisioner + } + return nil +} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/resource_syncer.go b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/resource_syncer.go new file mode 100644 index 00000000..0ec4c8f8 --- /dev/null +++ b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/resource_syncer.go @@ -0,0 +1,228 @@ +package connectorbuilder + +import ( + "context" + "fmt" + + v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" + "github.com/conductorone/baton-sdk/pkg/annotations" + "github.com/conductorone/baton-sdk/pkg/pagination" + "github.com/conductorone/baton-sdk/pkg/types/tasks" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" +) + +// ResourceSyncer is the primary interface for connector developers to implement. +// +// It defines the core functionality for synchronizing resources, entitlements, and grants +// from external systems into Baton. Every connector must implement at least this interface +// for each resource type it supports. +// +// Extensions to this interface include: +// - ResourceProvisioner/ResourceProvisionerV2: For adding/removing access +// - ResourceManager: For creating and managing resources +// - ResourceDeleter: For deleting resources +// - AccountManager: For account provisioning operations +// - CredentialManager: For credential rotation operations. +// - ResourceTargetedSyncer: For directly getting a resource supporting targeted sync. +type ResourceSyncer interface { + ResourceType(ctx context.Context) *v2.ResourceType + List(ctx context.Context, parentResourceID *v2.ResourceId, pToken *pagination.Token) ([]*v2.Resource, string, annotations.Annotations, error) + Entitlements(ctx context.Context, resource *v2.Resource, pToken *pagination.Token) ([]*v2.Entitlement, string, annotations.Annotations, error) + Grants(ctx context.Context, resource *v2.Resource, pToken *pagination.Token) ([]*v2.Grant, string, annotations.Annotations, error) +} + +// ResourceTargetedSyncer extends ResourceSyncer to add capabilities for directly syncing an individual resource +// +// Implementing this interface indicates the connector supports calling "get" on a resource +// of the associated resource type. +type ResourceTargetedSyncer interface { + ResourceSyncer + Get(ctx context.Context, resourceId *v2.ResourceId, parentResourceId *v2.ResourceId) (*v2.Resource, annotations.Annotations, error) +} + +// ListResourceTypes lists all available resource types. +func (b *builder) ListResourceTypes( + ctx context.Context, + request *v2.ResourceTypesServiceListResourceTypesRequest, +) (*v2.ResourceTypesServiceListResourceTypesResponse, error) { + ctx, span := tracer.Start(ctx, "builder.ListResourceTypes") + defer span.End() + + start := b.nowFunc() + tt := tasks.ListResourceTypesType + var out []*v2.ResourceType + + if len(b.resourceBuilders) == 0 { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: no resource builders found") + } + + for _, rb := range b.resourceBuilders { + out = append(out, rb.ResourceType(ctx)) + } + + if len(out) == 0 { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: no resource types found") + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.ResourceTypesServiceListResourceTypesResponse{List: out}, nil +} + +// ListResources returns all available resources for a given resource type ID. +func (b *builder) ListResources(ctx context.Context, request *v2.ResourcesServiceListResourcesRequest) (*v2.ResourcesServiceListResourcesResponse, error) { + ctx, span := tracer.Start(ctx, "builder.ListResources") + defer span.End() + + start := b.nowFunc() + tt := tasks.ListResourcesType + rb, ok := b.resourceBuilders[request.ResourceTypeId] + if !ok { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: list resources with unknown resource type %s", request.ResourceTypeId) + } + out, nextPageToken, annos, err := rb.List(ctx, request.ParentResourceId, &pagination.Token{ + Size: int(request.PageSize), + Token: request.PageToken, + }) + resp := &v2.ResourcesServiceListResourcesResponse{ + List: out, + NextPageToken: nextPageToken, + Annotations: annos, + } + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return resp, fmt.Errorf("error: listing resources failed: %w", err) + } + if request.PageToken != "" && request.PageToken == nextPageToken { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return resp, fmt.Errorf("error: listing resources failed: next page token is the same as the current page token. this is most likely a connector bug") + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return resp, nil +} + +func (b *builder) GetResource(ctx context.Context, request *v2.ResourceGetterServiceGetResourceRequest) (*v2.ResourceGetterServiceGetResourceResponse, error) { + ctx, span := tracer.Start(ctx, "builder.GetResource") + defer span.End() + + start := b.nowFunc() + tt := tasks.GetResourceType + resourceType := request.GetResourceId().GetResourceType() + rb, ok := b.resourceTargetedSyncers[resourceType] + if !ok { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, status.Errorf(codes.Unimplemented, "error: get resource with unknown resource type %s", resourceType) + } + + resource, annos, err := rb.Get(ctx, request.GetResourceId(), request.GetParentResourceId()) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: get resource failed: %w", err) + } + if resource == nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, status.Error(codes.NotFound, "error: get resource returned nil") + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.ResourceGetterServiceGetResourceResponse{ + Resource: resource, + Annotations: annos, + }, nil +} + +// ListEntitlements returns all the entitlements for a given resource. +func (b *builder) ListEntitlements(ctx context.Context, request *v2.EntitlementsServiceListEntitlementsRequest) (*v2.EntitlementsServiceListEntitlementsResponse, error) { + ctx, span := tracer.Start(ctx, "builder.ListEntitlements") + defer span.End() + + start := b.nowFunc() + tt := tasks.ListEntitlementsType + rb, ok := b.resourceBuilders[request.Resource.Id.ResourceType] + if !ok { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: list entitlements with unknown resource type %s", request.Resource.Id.ResourceType) + } + + out, nextPageToken, annos, err := rb.Entitlements(ctx, request.Resource, &pagination.Token{ + Size: int(request.PageSize), + Token: request.PageToken, + }) + resp := &v2.EntitlementsServiceListEntitlementsResponse{ + List: out, + NextPageToken: nextPageToken, + Annotations: annos, + } + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return resp, fmt.Errorf("error: listing entitlements failed: %w", err) + } + if request.PageToken != "" && request.PageToken == nextPageToken { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return resp, fmt.Errorf("error: listing entitlements failed: next page token is the same as the current page token. this is most likely a connector bug") + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return resp, nil +} + +// ListGrants lists all the grants for a given resource. +func (b *builder) ListGrants(ctx context.Context, request *v2.GrantsServiceListGrantsRequest) (*v2.GrantsServiceListGrantsResponse, error) { + ctx, span := tracer.Start(ctx, "builder.ListGrants") + defer span.End() + + start := b.nowFunc() + tt := tasks.ListGrantsType + rid := request.Resource.Id + rb, ok := b.resourceBuilders[rid.ResourceType] + if !ok { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: list grants with unknown resource type %s", rid.ResourceType) + } + + out, nextPageToken, annos, err := rb.Grants(ctx, request.Resource, &pagination.Token{ + Size: int(request.PageSize), + Token: request.PageToken, + }) + + resp := &v2.GrantsServiceListGrantsResponse{ + List: out, + NextPageToken: nextPageToken, + Annotations: annos, + } + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return resp, fmt.Errorf("error: listing grants for resource %s/%s failed: %w", rid.ResourceType, rid.Resource, err) + } + if request.PageToken != "" && request.PageToken == nextPageToken { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return resp, fmt.Errorf("error: listing grants for resource %s/%s failed: next page token is the same as the current page token. this is most likely a connector bug", + rid.ResourceType, + rid.Resource) + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return resp, nil +} + +func (b *builder) addTargetedSyncer(_ context.Context, typeId string, rb ResourceSyncer) error { + if targetedSyncer, ok := rb.(ResourceTargetedSyncer); ok { + if _, ok := b.resourceTargetedSyncers[typeId]; ok { + return fmt.Errorf("error: duplicate resource type found for resource targeted syncer %s", typeId) + } + b.resourceTargetedSyncers[typeId] = targetedSyncer + } + return nil +} + +func (b *builder) addResourceBuilders(_ context.Context, typeId string, rb ResourceSyncer) error { + if _, ok := b.resourceBuilders[typeId]; ok { + return fmt.Errorf("error: duplicate resource type found for resource builder %s", typeId) + } + b.resourceBuilders[typeId] = rb + return nil +} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/tickets.go b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/tickets.go new file mode 100644 index 00000000..b5d735d9 --- /dev/null +++ b/vendor/github.com/conductorone/baton-sdk/pkg/connectorbuilder/tickets.go @@ -0,0 +1,238 @@ +package connectorbuilder + +import ( + "context" + "fmt" + "time" + + v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2" + "github.com/conductorone/baton-sdk/pkg/annotations" + "github.com/conductorone/baton-sdk/pkg/pagination" + "github.com/conductorone/baton-sdk/pkg/retry" + "github.com/conductorone/baton-sdk/pkg/types/tasks" +) + +// TicketManager extends ConnectorBuilder to add capabilities for ticket management. +// +// Implementing this interface indicates the connector can integrate with an external +// ticketing system, allowing Baton to create and track tickets in that system. +type TicketManager interface { + ConnectorBuilder + GetTicket(ctx context.Context, ticketId string) (*v2.Ticket, annotations.Annotations, error) + CreateTicket(ctx context.Context, ticket *v2.Ticket, schema *v2.TicketSchema) (*v2.Ticket, annotations.Annotations, error) + GetTicketSchema(ctx context.Context, schemaID string) (*v2.TicketSchema, annotations.Annotations, error) + ListTicketSchemas(ctx context.Context, pToken *pagination.Token) ([]*v2.TicketSchema, string, annotations.Annotations, error) + BulkCreateTickets(context.Context, *v2.TicketsServiceBulkCreateTicketsRequest) (*v2.TicketsServiceBulkCreateTicketsResponse, error) + BulkGetTickets(context.Context, *v2.TicketsServiceBulkGetTicketsRequest) (*v2.TicketsServiceBulkGetTicketsResponse, error) +} + +func (b *builder) BulkCreateTickets(ctx context.Context, request *v2.TicketsServiceBulkCreateTicketsRequest) (*v2.TicketsServiceBulkCreateTicketsResponse, error) { + ctx, span := tracer.Start(ctx, "builder.BulkCreateTickets") + defer span.End() + + start := b.nowFunc() + tt := tasks.BulkCreateTicketsType + if b.ticketManager == nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: ticket manager not implemented") + } + + reqBody := request.GetTicketRequests() + if len(reqBody) == 0 { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: request body had no items") + } + + ticketsResponse, err := b.ticketManager.BulkCreateTickets(ctx, request) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: creating tickets failed: %w", err) + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.TicketsServiceBulkCreateTicketsResponse{ + Tickets: ticketsResponse.GetTickets(), + }, nil +} + +func (b *builder) BulkGetTickets(ctx context.Context, request *v2.TicketsServiceBulkGetTicketsRequest) (*v2.TicketsServiceBulkGetTicketsResponse, error) { + ctx, span := tracer.Start(ctx, "builder.BulkGetTickets") + defer span.End() + + start := b.nowFunc() + tt := tasks.BulkGetTicketsType + if b.ticketManager == nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: ticket manager not implemented") + } + + reqBody := request.GetTicketRequests() + if len(reqBody) == 0 { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: request body had no items") + } + + ticketsResponse, err := b.ticketManager.BulkGetTickets(ctx, request) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: fetching tickets failed: %w", err) + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.TicketsServiceBulkGetTicketsResponse{ + Tickets: ticketsResponse.GetTickets(), + }, nil +} + +func (b *builder) ListTicketSchemas(ctx context.Context, request *v2.TicketsServiceListTicketSchemasRequest) (*v2.TicketsServiceListTicketSchemasResponse, error) { + ctx, span := tracer.Start(ctx, "builder.ListTicketSchemas") + defer span.End() + + start := b.nowFunc() + tt := tasks.ListTicketSchemasType + if b.ticketManager == nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: ticket manager not implemented") + } + + retryer := retry.NewRetryer(ctx, retry.RetryConfig{ + MaxAttempts: 10, + InitialDelay: 15 * time.Second, + MaxDelay: 0, + }) + + for { + out, nextPageToken, annos, err := b.ticketManager.ListTicketSchemas(ctx, &pagination.Token{ + Size: int(request.PageSize), + Token: request.PageToken, + }) + if err == nil { + if request.PageToken != "" && request.PageToken == nextPageToken { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: listing ticket schemas failed: next page token is the same as the current page token. this is most likely a connector bug") + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.TicketsServiceListTicketSchemasResponse{ + List: out, + NextPageToken: nextPageToken, + Annotations: annos, + }, nil + } + if retryer.ShouldWaitAndRetry(ctx, err) { + continue + } + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: listing ticket schemas failed: %w", err) + } +} + +func (b *builder) CreateTicket(ctx context.Context, request *v2.TicketsServiceCreateTicketRequest) (*v2.TicketsServiceCreateTicketResponse, error) { + ctx, span := tracer.Start(ctx, "builder.CreateTicket") + defer span.End() + + start := b.nowFunc() + tt := tasks.CreateTicketType + if b.ticketManager == nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: ticket manager not implemented") + } + + reqBody := request.GetRequest() + if reqBody == nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: request body is nil") + } + cTicket := &v2.Ticket{ + DisplayName: reqBody.GetDisplayName(), + Description: reqBody.GetDescription(), + Status: reqBody.GetStatus(), + Labels: reqBody.GetLabels(), + CustomFields: reqBody.GetCustomFields(), + RequestedFor: reqBody.GetRequestedFor(), + } + + ticket, annos, err := b.ticketManager.CreateTicket(ctx, cTicket, request.GetSchema()) + var resp *v2.TicketsServiceCreateTicketResponse + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + if ticket != nil { + resp = &v2.TicketsServiceCreateTicketResponse{ + Ticket: ticket, + Annotations: annos, + } + } + return resp, fmt.Errorf("error: creating ticket failed: %w", err) + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.TicketsServiceCreateTicketResponse{ + Ticket: ticket, + Annotations: annos, + }, nil +} + +func (b *builder) GetTicket(ctx context.Context, request *v2.TicketsServiceGetTicketRequest) (*v2.TicketsServiceGetTicketResponse, error) { + ctx, span := tracer.Start(ctx, "builder.GetTicket") + defer span.End() + + start := b.nowFunc() + tt := tasks.GetTicketType + if b.ticketManager == nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: ticket manager not implemented") + } + + var resp *v2.TicketsServiceGetTicketResponse + ticket, annos, err := b.ticketManager.GetTicket(ctx, request.GetId()) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + if ticket != nil { + resp = &v2.TicketsServiceGetTicketResponse{ + Ticket: ticket, + Annotations: annos, + } + } + return resp, fmt.Errorf("error: getting ticket failed: %w", err) + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.TicketsServiceGetTicketResponse{ + Ticket: ticket, + Annotations: annos, + }, nil +} + +func (b *builder) GetTicketSchema(ctx context.Context, request *v2.TicketsServiceGetTicketSchemaRequest) (*v2.TicketsServiceGetTicketSchemaResponse, error) { + ctx, span := tracer.Start(ctx, "builder.GetTicketSchema") + defer span.End() + + start := b.nowFunc() + tt := tasks.GetTicketSchemaType + if b.ticketManager == nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: ticket manager not implemented") + } + + ticketSchema, annos, err := b.ticketManager.GetTicketSchema(ctx, request.GetId()) + if err != nil { + b.m.RecordTaskFailure(ctx, tt, b.nowFunc().Sub(start)) + return nil, fmt.Errorf("error: getting ticket metadata failed: %w", err) + } + + b.m.RecordTaskSuccess(ctx, tt, b.nowFunc().Sub(start)) + return &v2.TicketsServiceGetTicketSchemaResponse{ + Schema: ticketSchema, + Annotations: annos, + }, nil +} + +func (b *builder) addTicketManager(_ context.Context, c ConnectorBuilder) error { + if ticketManager, ok := c.(TicketManager); ok { + if b.ticketManager != nil { + return fmt.Errorf("error: cannot set multiple ticket managers") + } + b.ticketManager = ticketManager + } + return nil +} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/field/fields.go b/vendor/github.com/conductorone/baton-sdk/pkg/field/fields.go index 5aca646d..e88a76bd 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/field/fields.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/field/fields.go @@ -213,6 +213,27 @@ func StringField(name string, optional ...fieldOption) SchemaField { return field } +func FileUploadField(name string, bonusStrings []string, optional ...fieldOption) SchemaField { + field := SchemaField{ + FieldName: name, + Variant: StringVariant, + DefaultValue: "", + ExportTarget: ExportTargetGUI, + Rules: FieldRule{}, + SyncerConfig: syncerConfig{}, + ConnectorConfig: connectorConfig{ + FieldType: FileUpload, + BonusStrings: bonusStrings, + }, + } + + for _, o := range optional { + field = o(field) + } + + return field +} + func IntField(name string, optional ...fieldOption) SchemaField { field := SchemaField{ FieldName: name, diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/sdk/version.go b/vendor/github.com/conductorone/baton-sdk/pkg/sdk/version.go index 4bc7345e..c00dd9ef 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/sdk/version.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/sdk/version.go @@ -1,3 +1,3 @@ package sdk -const Version = "v0.4.7" +const Version = "v0.4.10" diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/session/grpc_session.go b/vendor/github.com/conductorone/baton-sdk/pkg/session/grpc_session.go index 70b8a868..af2563ae 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/session/grpc_session.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/session/grpc_session.go @@ -15,7 +15,7 @@ import ( v1 "github.com/conductorone/baton-sdk/pb/c1/connectorapi/baton/v1" "github.com/conductorone/baton-sdk/pkg/sdk" - "github.com/conductorone/baton-sdk/pkg/types" + "github.com/conductorone/baton-sdk/pkg/types/sessions" dpop_grpc "github.com/conductorone/dpop/integrations/dpop_grpc" "github.com/conductorone/dpop/pkg/dpop" "github.com/go-jose/go-jose/v4" @@ -35,7 +35,7 @@ type GRPCSessionCache struct { // It reuses an existing access token and DPoP key instead of performing a new authentication round. // It reads the session service address from the BATON_SESSION_SERVICE_ADDR environment variable, // defaulting to "localhost:50051" if not set. -func NewGRPCSessionClient(ctx context.Context, accessToken string, dpopKey *jose.JSONWebKey, opt ...types.SessionCacheConstructorOption) (v1.BatonSessionServiceClient, error) { +func NewGRPCSessionClient(ctx context.Context, accessToken string, dpopKey *jose.JSONWebKey, opt ...sessions.SessionStoreConstructorOption) (v1.BatonSessionServiceClient, error) { // Apply constructor options for _, option := range opt { var err error @@ -123,7 +123,7 @@ func (s *staticTokenSource) Token() (*oauth2.Token, error) { // These functions are no longer needed since we're reusing existing credentials // NewGRPCSessionCache creates a new gRPC session cache instance. -func NewGRPCSessionCache(ctx context.Context, client v1.BatonSessionServiceClient, opt ...types.SessionCacheConstructorOption) (types.SessionCache, error) { +func NewGRPCSessionCache(ctx context.Context, client v1.BatonSessionServiceClient, opt ...sessions.SessionStoreConstructorOption) (sessions.SessionStore, error) { // Apply constructor options for _, option := range opt { var err error @@ -139,7 +139,7 @@ func NewGRPCSessionCache(ctx context.Context, client v1.BatonSessionServiceClien } // Get retrieves a value from the cache by key. -func (g *GRPCSessionCache) Get(ctx context.Context, key string, opt ...types.SessionCacheOption) ([]byte, bool, error) { +func (g *GRPCSessionCache) Get(ctx context.Context, key string, opt ...sessions.SessionStoreOption) ([]byte, bool, error) { bag, err := applyOptions(ctx, opt...) if err != nil { return nil, false, err @@ -167,7 +167,7 @@ func (g *GRPCSessionCache) Get(ctx context.Context, key string, opt ...types.Ses } // GetMany retrieves multiple values from the cache by keys. -func (g *GRPCSessionCache) GetMany(ctx context.Context, keys []string, opt ...types.SessionCacheOption) (map[string][]byte, error) { +func (g *GRPCSessionCache) GetMany(ctx context.Context, keys []string, opt ...sessions.SessionStoreOption) (map[string][]byte, error) { bag, err := applyOptions(ctx, opt...) if err != nil { return nil, err @@ -212,7 +212,7 @@ func (g *GRPCSessionCache) GetMany(ctx context.Context, keys []string, opt ...ty } // Set stores a value in the cache with the given key. -func (g *GRPCSessionCache) Set(ctx context.Context, key string, value []byte, opt ...types.SessionCacheOption) error { +func (g *GRPCSessionCache) Set(ctx context.Context, key string, value []byte, opt ...sessions.SessionStoreOption) error { bag, err := applyOptions(ctx, opt...) if err != nil { return err @@ -237,7 +237,7 @@ func (g *GRPCSessionCache) Set(ctx context.Context, key string, value []byte, op } // SetMany stores multiple values in the cache. -func (g *GRPCSessionCache) SetMany(ctx context.Context, values map[string][]byte, opt ...types.SessionCacheOption) error { +func (g *GRPCSessionCache) SetMany(ctx context.Context, values map[string][]byte, opt ...sessions.SessionStoreOption) error { bag, err := applyOptions(ctx, opt...) if err != nil { return err @@ -267,7 +267,7 @@ func (g *GRPCSessionCache) SetMany(ctx context.Context, values map[string][]byte } // Delete removes a value from the cache by key. -func (g *GRPCSessionCache) Delete(ctx context.Context, key string, opt ...types.SessionCacheOption) error { +func (g *GRPCSessionCache) Delete(ctx context.Context, key string, opt ...sessions.SessionStoreOption) error { bag, err := applyOptions(ctx, opt...) if err != nil { return err @@ -291,7 +291,7 @@ func (g *GRPCSessionCache) Delete(ctx context.Context, key string, opt ...types. } // Clear removes all values from the cache. -func (g *GRPCSessionCache) Clear(ctx context.Context, opt ...types.SessionCacheOption) error { +func (g *GRPCSessionCache) Clear(ctx context.Context, opt ...sessions.SessionStoreOption) error { bag, err := applyOptions(ctx, opt...) if err != nil { return err @@ -313,7 +313,7 @@ func (g *GRPCSessionCache) Clear(ctx context.Context, opt ...types.SessionCacheO // Note: The gRPC service doesn't have a GetAll method, so we'll need to implement this // by getting all keys first and then using GetMany. This is a limitation of the current // gRPC service definition. -func (g *GRPCSessionCache) GetAll(ctx context.Context, opt ...types.SessionCacheOption) (map[string][]byte, error) { +func (g *GRPCSessionCache) GetAll(ctx context.Context, opt ...sessions.SessionStoreOption) (map[string][]byte, error) { bag, err := applyOptions(ctx, opt...) if err != nil { return nil, err diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/session/json.go b/vendor/github.com/conductorone/baton-sdk/pkg/session/json.go index 285bbac3..ba74f5b5 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/session/json.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/session/json.go @@ -4,10 +4,10 @@ import ( "context" "encoding/json" - "github.com/conductorone/baton-sdk/pkg/types" + "github.com/conductorone/baton-sdk/pkg/types/sessions" ) -func GetManyJSON[T any](ctx context.Context, keys []string, opt ...types.SessionCacheOption) (map[string]T, error) { +func GetManyJSON[T any](ctx context.Context, keys []string, opt ...sessions.SessionStoreOption) (map[string]T, error) { cache, err := GetSession(ctx) if err != nil { return nil, err @@ -32,7 +32,7 @@ func GetManyJSON[T any](ctx context.Context, keys []string, opt ...types.Session return result, nil } -func SetManyJSON[T any](ctx context.Context, items map[string]T, opt ...types.SessionCacheOption) error { +func SetManyJSON[T any](ctx context.Context, items map[string]T, opt ...sessions.SessionStoreOption) error { cache, err := GetSession(ctx) if err != nil { return err @@ -52,7 +52,7 @@ func SetManyJSON[T any](ctx context.Context, items map[string]T, opt ...types.Se return cache.SetMany(ctx, bytesMap, opt...) } -func GetJSON[T any](ctx context.Context, key string, opt ...types.SessionCacheOption) (T, bool, error) { +func GetJSON[T any](ctx context.Context, key string, opt ...sessions.SessionStoreOption) (T, bool, error) { var zero T cache, err := GetSession(ctx) if err != nil { @@ -75,7 +75,7 @@ func GetJSON[T any](ctx context.Context, key string, opt ...types.SessionCacheOp return item, true, nil } -func SetJSON[T any](ctx context.Context, key string, item T, opt ...types.SessionCacheOption) error { +func SetJSON[T any](ctx context.Context, key string, item T, opt ...sessions.SessionStoreOption) error { cache, err := GetSession(ctx) if err != nil { return err @@ -91,7 +91,7 @@ func SetJSON[T any](ctx context.Context, key string, item T, opt ...types.Sessio return cache.Set(ctx, key, bytes, opt...) } -func DeleteJSON(ctx context.Context, key string, opt ...types.SessionCacheOption) error { +func DeleteJSON(ctx context.Context, key string, opt ...sessions.SessionStoreOption) error { cache, err := GetSession(ctx) if err != nil { return err @@ -100,7 +100,7 @@ func DeleteJSON(ctx context.Context, key string, opt ...types.SessionCacheOption return cache.Delete(ctx, key, opt...) } -func ClearJSON(ctx context.Context, opt ...types.SessionCacheOption) error { +func ClearJSON(ctx context.Context, opt ...sessions.SessionStoreOption) error { cache, err := GetSession(ctx) if err != nil { return err @@ -109,7 +109,7 @@ func ClearJSON(ctx context.Context, opt ...types.SessionCacheOption) error { return cache.Clear(ctx, opt...) } -func GetAllJSON[T any](ctx context.Context, opt ...types.SessionCacheOption) (map[string]T, error) { +func GetAllJSON[T any](ctx context.Context, opt ...sessions.SessionStoreOption) (map[string]T, error) { cache, err := GetSession(ctx) if err != nil { return nil, err diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/session/memory.go b/vendor/github.com/conductorone/baton-sdk/pkg/session/memory.go index 6aa5a4b5..0b784ccd 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/session/memory.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/session/memory.go @@ -7,7 +7,7 @@ import ( "sync" "time" - "github.com/conductorone/baton-sdk/pkg/types" + "github.com/conductorone/baton-sdk/pkg/types/sessions" ) // MemorySessionCache implements SessionCache interface using an in-memory store with TTL. @@ -18,12 +18,12 @@ type MemorySessionCache struct { } // NewMemorySessionCache creates a new in-memory session cache with default TTL of 1 hour. -func NewMemorySessionCache(ctx context.Context, opt ...types.SessionCacheConstructorOption) (types.SessionCache, error) { +func NewMemorySessionCache(ctx context.Context, opt ...sessions.SessionStoreConstructorOption) (sessions.SessionStore, error) { return NewMemorySessionCacheWithTTL(ctx, time.Hour, opt...) } // NewMemorySessionCacheWithTTL creates a new in-memory session cache with custom TTL. -func NewMemorySessionCacheWithTTL(ctx context.Context, ttl time.Duration, opt ...types.SessionCacheConstructorOption) (types.SessionCache, error) { +func NewMemorySessionCacheWithTTL(ctx context.Context, ttl time.Duration, opt ...sessions.SessionStoreConstructorOption) (sessions.SessionStore, error) { // Apply constructor options for _, option := range opt { var err error @@ -40,7 +40,7 @@ func NewMemorySessionCacheWithTTL(ctx context.Context, ttl time.Duration, opt .. } // Get retrieves a value from the cache by key. -func (m *MemorySessionCache) Get(ctx context.Context, key string, opt ...types.SessionCacheOption) ([]byte, bool, error) { +func (m *MemorySessionCache) Get(ctx context.Context, key string, opt ...sessions.SessionStoreOption) ([]byte, bool, error) { bag, err := applyOptions(ctx, opt...) if err != nil { return nil, false, err @@ -68,7 +68,7 @@ func (m *MemorySessionCache) Get(ctx context.Context, key string, opt ...types.S } // Set stores a value in the cache with the given key. -func (m *MemorySessionCache) Set(ctx context.Context, key string, value []byte, opt ...types.SessionCacheOption) error { +func (m *MemorySessionCache) Set(ctx context.Context, key string, value []byte, opt ...sessions.SessionStoreOption) error { bag, err := applyOptions(ctx, opt...) if err != nil { return err @@ -93,7 +93,7 @@ func (m *MemorySessionCache) Set(ctx context.Context, key string, value []byte, } // Delete removes a value from the cache by key. -func (m *MemorySessionCache) Delete(ctx context.Context, key string, opt ...types.SessionCacheOption) error { +func (m *MemorySessionCache) Delete(ctx context.Context, key string, opt ...sessions.SessionStoreOption) error { bag, err := applyOptions(ctx, opt...) if err != nil { return err @@ -113,7 +113,7 @@ func (m *MemorySessionCache) Delete(ctx context.Context, key string, opt ...type } // Clear removes all values from the cache. -func (m *MemorySessionCache) Clear(ctx context.Context, opt ...types.SessionCacheOption) error { +func (m *MemorySessionCache) Clear(ctx context.Context, opt ...sessions.SessionStoreOption) error { bag, err := applyOptions(ctx, opt...) if err != nil { return err @@ -127,7 +127,7 @@ func (m *MemorySessionCache) Clear(ctx context.Context, opt ...types.SessionCach } // GetAll returns all key-value pairs. -func (m *MemorySessionCache) GetAll(ctx context.Context, opt ...types.SessionCacheOption) (map[string][]byte, error) { +func (m *MemorySessionCache) GetAll(ctx context.Context, opt ...sessions.SessionStoreOption) (map[string][]byte, error) { bag, err := applyOptions(ctx, opt...) if err != nil { return nil, err @@ -155,7 +155,7 @@ func (m *MemorySessionCache) GetAll(ctx context.Context, opt ...types.SessionCac } // GetMany retrieves multiple values from the cache by keys. -func (m *MemorySessionCache) GetMany(ctx context.Context, keys []string, opt ...types.SessionCacheOption) (map[string][]byte, error) { +func (m *MemorySessionCache) GetMany(ctx context.Context, keys []string, opt ...sessions.SessionStoreOption) (map[string][]byte, error) { bag, err := applyOptions(ctx, opt...) if err != nil { return nil, err @@ -183,7 +183,7 @@ func (m *MemorySessionCache) GetMany(ctx context.Context, keys []string, opt ... } // SetMany stores multiple values in the cache. -func (m *MemorySessionCache) SetMany(ctx context.Context, values map[string][]byte, opt ...types.SessionCacheOption) error { +func (m *MemorySessionCache) SetMany(ctx context.Context, values map[string][]byte, opt ...sessions.SessionStoreOption) error { // Apply options to get syncID bag, err := applyOptions(ctx, opt...) if err != nil { diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/session/session.go b/vendor/github.com/conductorone/baton-sdk/pkg/session/session.go index 92ee1bfc..8b633dbb 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/session/session.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/session/session.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/conductorone/baton-sdk/pkg/types" + "github.com/conductorone/baton-sdk/pkg/types/sessions" ) // KeyPrefixDelimiter is the delimiter used to separate prefixes from keys in the session cache. @@ -12,22 +12,22 @@ const KeyPrefixDelimiter = "::" // GetSession retrieves the session cache instance from the context. // Returns an error if no session cache is found in the context. -func GetSession(ctx context.Context) (types.SessionCache, error) { - if sessionCache, ok := ctx.Value(types.SessionCacheKey{}).(types.SessionCache); ok { +func GetSession(ctx context.Context) (sessions.SessionStore, error) { + if sessionCache, ok := ctx.Value(sessions.SessionStoreKey{}).(sessions.SessionStore); ok { return sessionCache, nil } return nil, fmt.Errorf("no session cache found in context") } -func WithSyncID(syncID string) types.SessionCacheOption { - return func(ctx context.Context, bag *types.SessionCacheBag) error { +func WithSyncID(syncID string) sessions.SessionStoreOption { + return func(ctx context.Context, bag *sessions.SessionStoreBag) error { bag.SyncID = syncID return nil } } -func WithPrefix(prefix string) types.SessionCacheOption { - return func(ctx context.Context, bag *types.SessionCacheBag) error { +func WithPrefix(prefix string) sessions.SessionStoreOption { + return func(ctx context.Context, bag *sessions.SessionStoreBag) error { bag.Prefix = prefix return nil } @@ -35,15 +35,15 @@ func WithPrefix(prefix string) types.SessionCacheOption { // GetSyncIDFromContext retrieves the sync ID from the context, returning empty string if not found. func GetSyncIDFromContext(ctx context.Context) string { - if syncID, ok := ctx.Value(types.SyncIDKey{}).(string); ok { + if syncID, ok := ctx.Value(sessions.SyncIDKey{}).(string); ok { return syncID } return "" } // applyOptions applies session cache options and returns a configured bag. -func applyOptions(ctx context.Context, opt ...types.SessionCacheOption) (*types.SessionCacheBag, error) { - bag := &types.SessionCacheBag{} +func applyOptions(ctx context.Context, opt ...sessions.SessionStoreOption) (*sessions.SessionStoreBag, error) { + bag := &sessions.SessionStoreBag{} for _, option := range opt { err := option(ctx, bag) diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/sync/syncer.go b/vendor/github.com/conductorone/baton-sdk/pkg/sync/syncer.go index c8cbd1f4..be5dbe3a 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/sync/syncer.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/sync/syncer.go @@ -21,6 +21,7 @@ import ( "github.com/conductorone/baton-sdk/pkg/types/entitlement" batonGrant "github.com/conductorone/baton-sdk/pkg/types/grant" "github.com/conductorone/baton-sdk/pkg/types/resource" + "github.com/conductorone/baton-sdk/pkg/types/sessions" mapset "github.com/deckarep/golang-set/v2" "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" "go.opentelemetry.io/otel" @@ -392,7 +393,7 @@ func (s *syncer) Sync(ctx context.Context) error { // Add ActiveSync to context once after we have the syncID if syncID != "" { - ctx = types.SetSyncIDInContext(ctx, syncID) + ctx = sessions.SetSyncIDInContext(ctx, syncID) } span.SetAttributes(attribute.String("sync_id", syncID)) @@ -2730,6 +2731,13 @@ func (s *syncer) Close(ctx context.Context) error { } } + if s.externalResourceReader != nil { + err = s.externalResourceReader.Close() + if err != nil { + return fmt.Errorf("error closing external resource reader: %w", err) + } + } + if s.c1zManager != nil { err = s.c1zManager.SaveC1Z(ctx) if err != nil { diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/synccompactor/compactor.go b/vendor/github.com/conductorone/baton-sdk/pkg/synccompactor/compactor.go index 1d638341..726d58ce 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/synccompactor/compactor.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/synccompactor/compactor.go @@ -128,6 +128,7 @@ func (c *Compactor) Compact(ctx context.Context) (*CompactableSync, error) { ctx, emptyConnector, sync.WithC1ZPath(applied.FilePath), + sync.WithTmpDir(c.tmpDir), sync.WithSyncID(applied.SyncID), sync.WithOnlyExpandGrants(), ) diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/types/session_cache.go b/vendor/github.com/conductorone/baton-sdk/pkg/types/session_cache.go deleted file mode 100644 index 86d69eb5..00000000 --- a/vendor/github.com/conductorone/baton-sdk/pkg/types/session_cache.go +++ /dev/null @@ -1,58 +0,0 @@ -package types - -import ( - "context" -) - -// SessionCacheKey is the context key for storing the session cache instance. -type SessionCacheKey struct{} - -// SessionCache is an interface for caching session data. -type SessionCache interface { - Get(ctx context.Context, key string, opt ...SessionCacheOption) ([]byte, bool, error) - GetMany(ctx context.Context, keys []string, opt ...SessionCacheOption) (map[string][]byte, error) - Set(ctx context.Context, key string, value []byte, opt ...SessionCacheOption) error - SetMany(ctx context.Context, values map[string][]byte, opt ...SessionCacheOption) error - Delete(ctx context.Context, key string, opt ...SessionCacheOption) error - Clear(ctx context.Context, opt ...SessionCacheOption) error - GetAll(ctx context.Context, opt ...SessionCacheOption) (map[string][]byte, error) - Close(ctx context.Context) error -} - -// SessionCacheOption is a function that modifies a SessionCacheBag. -type SessionCacheOption func(ctx context.Context, bag *SessionCacheBag) error - -// SessionCacheConstructor is a function that creates a SessionCache instance. -type SessionCacheConstructor func(ctx context.Context, opt ...SessionCacheConstructorOption) (SessionCache, error) - -// SessionCacheConstructorOption is a function that modifies the context for session cache construction. -type SessionCacheConstructorOption func(ctx context.Context) (context.Context, error) - -// SessionCacheBag holds the configuration for session cache operations. -type SessionCacheBag struct { - SyncID string - Prefix string -} - -// SyncIDKey is the context key for storing the current sync ID. -type SyncIDKey struct{} - -// WithSyncID returns a SessionCacheOption that sets the sync ID for the operation. -func WithSyncID(syncID string) SessionCacheOption { - return func(ctx context.Context, bag *SessionCacheBag) error { - bag.SyncID = syncID - return nil - } -} - -// GetSyncID retrieves the sync ID from the context, returning empty string if not found. -func GetSyncID(ctx context.Context) string { - if syncID, ok := ctx.Value(SyncIDKey{}).(string); ok { - return syncID - } - return "" -} - -func SetSyncIDInContext(ctx context.Context, syncID string) context.Context { - return context.WithValue(ctx, SyncIDKey{}, syncID) -} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/types/sessions/sessions.go b/vendor/github.com/conductorone/baton-sdk/pkg/types/sessions/sessions.go new file mode 100644 index 00000000..75efada2 --- /dev/null +++ b/vendor/github.com/conductorone/baton-sdk/pkg/types/sessions/sessions.go @@ -0,0 +1,52 @@ +package sessions + +import ( + "context" +) + +type SessionStoreKey struct{} + +type SessionStore interface { + Get(ctx context.Context, key string, opt ...SessionStoreOption) ([]byte, bool, error) + GetMany(ctx context.Context, keys []string, opt ...SessionStoreOption) (map[string][]byte, error) + Set(ctx context.Context, key string, value []byte, opt ...SessionStoreOption) error + SetMany(ctx context.Context, values map[string][]byte, opt ...SessionStoreOption) error + Delete(ctx context.Context, key string, opt ...SessionStoreOption) error + Clear(ctx context.Context, opt ...SessionStoreOption) error + GetAll(ctx context.Context, opt ...SessionStoreOption) (map[string][]byte, error) + Close(ctx context.Context) error +} + +type SessionStoreOption func(ctx context.Context, bag *SessionStoreBag) error + +type SessionStoreConstructor func(ctx context.Context, opt ...SessionStoreConstructorOption) (SessionStore, error) + +type SessionStoreConstructorOption func(ctx context.Context) (context.Context, error) + +type SessionStoreBag struct { + SyncID string + Prefix string +} + +// SyncIDKey is the context key for storing the current sync ID. +type SyncIDKey struct{} + +// WithSyncID returns a SessionCacheOption that sets the sync ID for the operation. +func WithSyncID(syncID string) SessionStoreOption { + return func(ctx context.Context, bag *SessionStoreBag) error { + bag.SyncID = syncID + return nil + } +} + +// GetSyncID retrieves the sync ID from the context, returning empty string if not found. +func GetSyncID(ctx context.Context) string { + if syncID, ok := ctx.Value(SyncIDKey{}).(string); ok { + return syncID + } + return "" +} + +func SetSyncIDInContext(ctx context.Context, syncID string) context.Context { + return context.WithValue(ctx, SyncIDKey{}, syncID) +} diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/ugrpc/interceptors.go b/vendor/github.com/conductorone/baton-sdk/pkg/ugrpc/interceptors.go index 1f412c22..a7f046df 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/ugrpc/interceptors.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/ugrpc/interceptors.go @@ -3,7 +3,7 @@ package ugrpc import ( "context" - "github.com/conductorone/baton-sdk/pkg/types" + "github.com/conductorone/baton-sdk/pkg/types/sessions" "github.com/grpc-ecosystem/go-grpc-middleware/logging/zap/ctxzap" grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" grpc_ctxtags "github.com/grpc-ecosystem/go-grpc-middleware/tags" @@ -39,8 +39,8 @@ func SessionCacheUnaryInterceptor(serverCtx context.Context) grpc.UnaryServerInt // Propagate session cache from server context to handler context ctx = ContextWithSyncID(ctx, req) - if sessionCache, ok := serverCtx.Value(types.SessionCacheKey{}).(types.SessionCache); ok { - ctx = context.WithValue(ctx, types.SessionCacheKey{}, sessionCache) + if sessionCache, ok := serverCtx.Value(sessions.SessionStoreKey{}).(sessions.SessionStore); ok { + ctx = context.WithValue(ctx, sessions.SessionStoreKey{}, sessionCache) } return handler(ctx, req) @@ -53,8 +53,8 @@ func SessionCacheStreamInterceptor(serverCtx context.Context) grpc.StreamServerI ctx := ss.Context() // Propagate session cache from server context to stream context - if sessionCache, ok := serverCtx.Value(types.SessionCacheKey{}).(types.SessionCache); ok { - ctx = context.WithValue(ctx, types.SessionCacheKey{}, sessionCache) + if sessionCache, ok := serverCtx.Value(sessions.SessionStoreKey{}).(sessions.SessionStore); ok { + ctx = context.WithValue(ctx, sessions.SessionStoreKey{}, sessionCache) } // Create a wrapped stream that handles both session cache and annotation extraction @@ -151,5 +151,5 @@ func ContextWithSyncID(ctx context.Context, req any) context.Context { return ctx } - return context.WithValue(ctx, types.SyncIDKey{}, syncID) + return context.WithValue(ctx, sessions.SyncIDKey{}, syncID) } diff --git a/vendor/github.com/conductorone/baton-sdk/pkg/uotel/config.go b/vendor/github.com/conductorone/baton-sdk/pkg/uotel/config.go index 14082a35..9d107225 100644 --- a/vendor/github.com/conductorone/baton-sdk/pkg/uotel/config.go +++ b/vendor/github.com/conductorone/baton-sdk/pkg/uotel/config.go @@ -251,7 +251,7 @@ func (c *otelConfig) initLogging(ctx context.Context, cc *grpc.ClientConn) (cont return nil, fmt.Errorf("failed to initialize otlp exporter: %w", err) } // TODO(morgabra): Whole bunch of tunables _here_... - processor := log.NewBatchProcessor(exp, log.WithExportInterval(time.Second*5)) + processor := log.NewBatchProcessor(exp, log.WithExportInterval(time.Second*1)) // TODO(morgabra): Whole bunch of tunables ALSO HERE... provider := log.NewLoggerProvider( log.WithResource(res), diff --git a/vendor/modules.txt b/vendor/modules.txt index ecbbd0f9..16a501d7 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -178,8 +178,8 @@ github.com/benbjohnson/clock # github.com/cenkalti/backoff/v4 v4.3.0 ## explicit; go 1.18 github.com/cenkalti/backoff/v4 -# github.com/conductorone/baton-sdk v0.4.8 -## explicit; go 1.25 +# github.com/conductorone/baton-sdk v0.4.11 +## explicit; go 1.25.2 github.com/conductorone/baton-sdk/internal/connector github.com/conductorone/baton-sdk/pb/c1/c1z/v1 github.com/conductorone/baton-sdk/pb/c1/config/v1 @@ -232,6 +232,7 @@ github.com/conductorone/baton-sdk/pkg/types github.com/conductorone/baton-sdk/pkg/types/entitlement github.com/conductorone/baton-sdk/pkg/types/grant github.com/conductorone/baton-sdk/pkg/types/resource +github.com/conductorone/baton-sdk/pkg/types/sessions github.com/conductorone/baton-sdk/pkg/types/tasks github.com/conductorone/baton-sdk/pkg/types/ticket github.com/conductorone/baton-sdk/pkg/ugrpc