diff --git a/cmd/boulder-wfe2/main.go b/cmd/boulder-wfe2/main.go index 7e874e67a74..86948e8fcec 100644 --- a/cmd/boulder-wfe2/main.go +++ b/cmd/boulder-wfe2/main.go @@ -26,7 +26,7 @@ import ( "github.com/letsencrypt/boulder/ratelimits" bredis "github.com/letsencrypt/boulder/redis" sapb "github.com/letsencrypt/boulder/sa/proto" - salesforcepb "github.com/letsencrypt/boulder/salesforce/proto" + emailpb "github.com/letsencrypt/boulder/salesforce/email/proto" "github.com/letsencrypt/boulder/unpause" "github.com/letsencrypt/boulder/web" "github.com/letsencrypt/boulder/wfe2" @@ -310,11 +310,11 @@ func main() { cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to SA") sac := sapb.NewStorageAuthorityReadOnlyClient(saConn) - var eec salesforcepb.ExporterClient + var eec emailpb.ExporterClient if c.WFE.EmailExporter != nil { emailExporterConn, err := bgrpc.ClientSetup(c.WFE.EmailExporter, tlsConfig, stats, clk) cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to email-exporter") - eec = salesforcepb.NewExporterClient(emailExporterConn) + eec = emailpb.NewExporterClient(emailExporterConn) } if c.WFE.RedeemNonceService == nil { diff --git a/cmd/email-exporter/main.go b/cmd/email-exporter/main.go index 2a222b8b709..164ae12d24c 100644 --- a/cmd/email-exporter/main.go +++ b/cmd/email-exporter/main.go @@ -6,13 +6,11 @@ import ( "os" "github.com/jmhodges/clock" - "google.golang.org/protobuf/types/known/emptypb" "github.com/letsencrypt/boulder/cmd" bgrpc "github.com/letsencrypt/boulder/grpc" "github.com/letsencrypt/boulder/salesforce" emailpb "github.com/letsencrypt/boulder/salesforce/email/proto" - salesforcepb "github.com/letsencrypt/boulder/salesforce/proto" ) // Config holds the configuration for the email-exporter service. @@ -64,38 +62,6 @@ type Config struct { OpenTelemetry cmd.OpenTelemetryConfig } -// legacyEmailExporterServer is an adapter that implements the email.Exporter -// gRPC interface by delegating to an inner salesforce.Exporter server. -// -// TODO(#8410): Remove legacyEmailExporterServer once fully migrated to -// salesforcepb.Exporter. -type legacyEmailExporterServer struct { - emailpb.UnimplementedExporterServer - inner salesforcepb.ExporterServer -} - -// SendContacts is an interface adapter that forwards the request to the same -// method on the inner salesforce.Exporter server. -func (s legacyEmailExporterServer) SendContacts(ctx context.Context, req *emailpb.SendContactsRequest) (*emptypb.Empty, error) { - return s.inner.SendContacts(ctx, &salesforcepb.SendContactsRequest{Emails: req.GetEmails()}) -} - -// SendCase is an interface adapter that forwards the request to the same method -// on the inner salesforce.Exporter server. -func (s legacyEmailExporterServer) SendCase(ctx context.Context, req *emailpb.SendCaseRequest) (*emptypb.Empty, error) { - return s.inner.SendCase(ctx, &salesforcepb.SendCaseRequest{ - Origin: req.GetOrigin(), - Subject: req.GetSubject(), - Description: req.GetDescription(), - ContactEmail: req.GetContactEmail(), - Organization: req.GetOrganization(), - AccountId: req.GetAccountId(), - RateLimitName: req.GetRateLimitName(), - RateLimitTier: req.GetRateLimitTier(), - UseCase: req.GetUseCase(), - }) -} - func main() { configFile := flag.String("config", "", "Path to configuration file") grpcAddr := flag.String("addr", "", "gRPC listen address override") @@ -152,10 +118,7 @@ func main() { go server.Start(daemonCtx) start, err := bgrpc.NewServer(c.EmailExporter.GRPC, logger).Add( - &salesforcepb.Exporter_ServiceDesc, server).Add( - // TODO(#8410): Remove emailpb.Exporter once fully migrated to - // salesforcepb.Exporter. - &emailpb.Exporter_ServiceDesc, legacyEmailExporterServer{inner: server}).Build( + &emailpb.Exporter_ServiceDesc, server).Build( tlsConfig, scope, clk) cmd.FailOnError(err, "Configuring email-exporter gRPC server") diff --git a/cmd/sfe/main.go b/cmd/sfe/main.go index cfc5e5f0a2d..6abda25e771 100644 --- a/cmd/sfe/main.go +++ b/cmd/sfe/main.go @@ -17,7 +17,7 @@ import ( "github.com/letsencrypt/boulder/ratelimits" bredis "github.com/letsencrypt/boulder/redis" sapb "github.com/letsencrypt/boulder/sa/proto" - salesforcepb "github.com/letsencrypt/boulder/salesforce/proto" + emailpb "github.com/letsencrypt/boulder/salesforce/email/proto" "github.com/letsencrypt/boulder/sfe" "github.com/letsencrypt/boulder/sfe/zendesk" "github.com/letsencrypt/boulder/web" @@ -156,11 +156,11 @@ func main() { cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to SA") sac := sapb.NewStorageAuthorityReadOnlyClient(saConn) - var eec salesforcepb.ExporterClient + var eec emailpb.ExporterClient if c.SFE.EmailExporter != nil { emailExporterConn, err := bgrpc.ClientSetup(c.SFE.EmailExporter, tlsConfig, stats, clk) cmd.FailOnError(err, "Failed to load credentials and create gRPC connection to email-exporter") - eec = salesforcepb.NewExporterClient(emailExporterConn) + eec = emailpb.NewExporterClient(emailExporterConn) } var zendeskClient *zendesk.Client diff --git a/mocks/emailexporter.go b/mocks/emailexporter.go index a2891588845..6f27872db95 100644 --- a/mocks/emailexporter.go +++ b/mocks/emailexporter.go @@ -9,7 +9,7 @@ import ( "google.golang.org/protobuf/types/known/emptypb" "github.com/letsencrypt/boulder/salesforce" - salesforcepb "github.com/letsencrypt/boulder/salesforce/proto" + emailpb "github.com/letsencrypt/boulder/salesforce/email/proto" ) var _ salesforce.SalesforceClient = (*MockSalesforceClientImpl)(nil) @@ -18,7 +18,6 @@ var _ salesforce.SalesforceClient = (*MockSalesforceClientImpl)(nil) type MockSalesforceClientImpl struct { sync.Mutex CreatedContacts []string - CreatedCases []salesforce.Case } // NewMockSalesforceClientImpl returns a MockSalesforceClientImpl, which implements @@ -47,26 +46,7 @@ func (m *MockSalesforceClientImpl) GetCreatedContacts() []string { return slices.Clone(m.CreatedContacts) } -// SendCase adds a case payload to CreatedCases. -func (m *MockSalesforceClientImpl) SendCase(payload salesforce.Case) error { - m.Lock() - defer m.Unlock() - - m.CreatedCases = append(m.CreatedCases, payload) - return nil -} - -// GetCreatedCases is used for testing to retrieve the list of created cases in -// a thread-safe manner. -func (m *MockSalesforceClientImpl) GetCreatedCases() []salesforce.Case { - m.Lock() - defer m.Unlock() - - // Return a copy to avoid race conditions. - return slices.Clone(m.CreatedCases) -} - -var _ salesforcepb.ExporterClient = (*MockExporterClientImpl)(nil) +var _ emailpb.ExporterClient = (*MockExporterClientImpl)(nil) // MockExporterClientImpl is a mock implementation of ExporterClient. type MockExporterClientImpl struct { @@ -74,7 +54,7 @@ type MockExporterClientImpl struct { } // NewMockExporterImpl returns a MockExporterClientImpl as an ExporterClient. -func NewMockExporterImpl(salesforceClient salesforce.SalesforceClient) salesforcepb.ExporterClient { +func NewMockExporterImpl(salesforceClient salesforce.SalesforceClient) emailpb.ExporterClient { return &MockExporterClientImpl{ SalesforceClient: salesforceClient, } @@ -82,7 +62,7 @@ func NewMockExporterImpl(salesforceClient salesforce.SalesforceClient) salesforc // SendContacts submits emails to the inner salesforce.SalesforceClient, returning an // error if any fail. -func (m *MockExporterClientImpl) SendContacts(ctx context.Context, req *salesforcepb.SendContactsRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) { +func (m *MockExporterClientImpl) SendContacts(ctx context.Context, req *emailpb.SendContactsRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) { for _, e := range req.Emails { err := m.SalesforceClient.SendContact(e) if err != nil { @@ -91,18 +71,3 @@ func (m *MockExporterClientImpl) SendContacts(ctx context.Context, req *salesfor } return &emptypb.Empty{}, nil } - -// SendCase submits a Case using the inner salesforce.SalesforceClient. -func (m *MockExporterClientImpl) SendCase(ctx context.Context, req *salesforcepb.SendCaseRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) { - return &emptypb.Empty{}, m.SalesforceClient.SendCase(salesforce.Case{ - Origin: req.Origin, - Subject: req.Subject, - Description: req.Description, - ContactEmail: req.ContactEmail, - Organization: req.Organization, - AccountId: req.AccountId, - RateLimitName: req.RateLimitName, - RateLimitTier: req.RateLimitTier, - UseCase: req.UseCase, - }) -} diff --git a/salesforce/email/proto/emailexporter.pb.go b/salesforce/email/proto/emailexporter.pb.go index 75d14972499..e78a06f9bb0 100644 --- a/salesforce/email/proto/emailexporter.pb.go +++ b/salesforce/email/proto/emailexporter.pb.go @@ -1,9 +1,3 @@ -// NOTE: This service is deprecated in favor of salesforce.Exporter. It must be -// kept in sync with salesforce.Exporter until we have fully migrated. -// -// TODO(#8410): Remove this service once we've fully migrated to -// salesforce.Exporter - // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.5 @@ -72,114 +66,6 @@ func (x *SendContactsRequest) GetEmails() []string { return nil } -type SendCaseRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Origin string `protobuf:"bytes,1,opt,name=origin,proto3" json:"origin,omitempty"` - Subject string `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"` - Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` - ContactEmail string `protobuf:"bytes,4,opt,name=contactEmail,proto3" json:"contactEmail,omitempty"` - Organization string `protobuf:"bytes,5,opt,name=organization,proto3" json:"organization,omitempty"` - AccountId string `protobuf:"bytes,6,opt,name=accountId,proto3" json:"accountId,omitempty"` - RateLimitName string `protobuf:"bytes,7,opt,name=rateLimitName,proto3" json:"rateLimitName,omitempty"` - RateLimitTier string `protobuf:"bytes,8,opt,name=rateLimitTier,proto3" json:"rateLimitTier,omitempty"` - UseCase string `protobuf:"bytes,9,opt,name=useCase,proto3" json:"useCase,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *SendCaseRequest) Reset() { - *x = SendCaseRequest{} - mi := &file_emailexporter_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *SendCaseRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SendCaseRequest) ProtoMessage() {} - -func (x *SendCaseRequest) ProtoReflect() protoreflect.Message { - mi := &file_emailexporter_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SendCaseRequest.ProtoReflect.Descriptor instead. -func (*SendCaseRequest) Descriptor() ([]byte, []int) { - return file_emailexporter_proto_rawDescGZIP(), []int{1} -} - -func (x *SendCaseRequest) GetOrigin() string { - if x != nil { - return x.Origin - } - return "" -} - -func (x *SendCaseRequest) GetSubject() string { - if x != nil { - return x.Subject - } - return "" -} - -func (x *SendCaseRequest) GetDescription() string { - if x != nil { - return x.Description - } - return "" -} - -func (x *SendCaseRequest) GetContactEmail() string { - if x != nil { - return x.ContactEmail - } - return "" -} - -func (x *SendCaseRequest) GetOrganization() string { - if x != nil { - return x.Organization - } - return "" -} - -func (x *SendCaseRequest) GetAccountId() string { - if x != nil { - return x.AccountId - } - return "" -} - -func (x *SendCaseRequest) GetRateLimitName() string { - if x != nil { - return x.RateLimitName - } - return "" -} - -func (x *SendCaseRequest) GetRateLimitTier() string { - if x != nil { - return x.RateLimitTier - } - return "" -} - -func (x *SendCaseRequest) GetUseCase() string { - if x != nil { - return x.UseCase - } - return "" -} - var File_emailexporter_proto protoreflect.FileDescriptor var file_emailexporter_proto_rawDesc = string([]byte{ @@ -189,39 +75,16 @@ var file_emailexporter_proto_rawDesc = string([]byte{ 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x2d, 0x0a, 0x13, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x06, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x73, 0x22, 0xb1, 0x02, 0x0a, 0x0f, 0x53, 0x65, 0x6e, - 0x64, 0x43, 0x61, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, - 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x72, - 0x69, 0x67, 0x69, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x20, - 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x45, 0x6d, 0x61, 0x69, 0x6c, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x45, - 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x22, 0x0a, 0x0c, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x72, 0x67, 0x61, - 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x72, 0x61, 0x74, 0x65, 0x4c, 0x69, - 0x6d, 0x69, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, - 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0d, - 0x72, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x54, 0x69, 0x65, 0x72, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x54, 0x69, - 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x43, 0x61, 0x73, 0x65, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x75, 0x73, 0x65, 0x43, 0x61, 0x73, 0x65, 0x32, 0x8a, 0x01, 0x0a, - 0x08, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x0c, 0x53, 0x65, 0x6e, - 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x65, 0x6d, 0x61, 0x69, - 0x6c, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x73, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x3a, 0x0a, - 0x08, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x61, 0x73, 0x65, 0x12, 0x16, 0x2e, 0x65, 0x6d, 0x61, 0x69, - 0x6c, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x61, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, - 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x65, 0x74, 0x73, 0x65, 0x6e, 0x63, 0x72, - 0x79, 0x70, 0x74, 0x2f, 0x62, 0x6f, 0x75, 0x6c, 0x64, 0x65, 0x72, 0x2f, 0x73, 0x61, 0x6c, 0x65, - 0x73, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x52, 0x06, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x73, 0x32, 0x4e, 0x0a, 0x08, 0x45, 0x78, 0x70, 0x6f, + 0x72, 0x74, 0x65, 0x72, 0x12, 0x42, 0x0a, 0x0c, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x74, + 0x61, 0x63, 0x74, 0x73, 0x12, 0x1a, 0x2e, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x2e, 0x53, 0x65, 0x6e, + 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x42, 0x37, 0x5a, 0x35, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x65, 0x74, 0x73, 0x65, 0x6e, 0x63, 0x72, 0x79, + 0x70, 0x74, 0x2f, 0x62, 0x6f, 0x75, 0x6c, 0x64, 0x65, 0x72, 0x2f, 0x73, 0x61, 0x6c, 0x65, 0x73, + 0x66, 0x6f, 0x72, 0x63, 0x65, 0x2f, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x2f, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, }) var ( @@ -236,19 +99,16 @@ func file_emailexporter_proto_rawDescGZIP() []byte { return file_emailexporter_proto_rawDescData } -var file_emailexporter_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_emailexporter_proto_msgTypes = make([]protoimpl.MessageInfo, 1) var file_emailexporter_proto_goTypes = []any{ (*SendContactsRequest)(nil), // 0: email.SendContactsRequest - (*SendCaseRequest)(nil), // 1: email.SendCaseRequest - (*emptypb.Empty)(nil), // 2: google.protobuf.Empty + (*emptypb.Empty)(nil), // 1: google.protobuf.Empty } var file_emailexporter_proto_depIdxs = []int32{ 0, // 0: email.Exporter.SendContacts:input_type -> email.SendContactsRequest - 1, // 1: email.Exporter.SendCase:input_type -> email.SendCaseRequest - 2, // 2: email.Exporter.SendContacts:output_type -> google.protobuf.Empty - 2, // 3: email.Exporter.SendCase:output_type -> google.protobuf.Empty - 2, // [2:4] is the sub-list for method output_type - 0, // [0:2] is the sub-list for method input_type + 1, // 1: email.Exporter.SendContacts:output_type -> google.protobuf.Empty + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name @@ -265,7 +125,7 @@ func file_emailexporter_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_emailexporter_proto_rawDesc), len(file_emailexporter_proto_rawDesc)), NumEnums: 0, - NumMessages: 2, + NumMessages: 1, NumExtensions: 0, NumServices: 1, }, diff --git a/salesforce/email/proto/emailexporter.proto b/salesforce/email/proto/emailexporter.proto index 8c3733444e8..208a43d373e 100644 --- a/salesforce/email/proto/emailexporter.proto +++ b/salesforce/email/proto/emailexporter.proto @@ -1,34 +1,14 @@ -// NOTE: This service is deprecated in favor of salesforce.Exporter. It must be -// kept in sync with salesforce.Exporter until we have fully migrated. -// -// TODO(#8410): Remove this service once we've fully migrated to -// salesforce.Exporter - syntax = "proto3"; package email; -option go_package = "github.com/letsencrypt/boulder/salesforce/proto"; +option go_package = "github.com/letsencrypt/boulder/salesforce/email/proto"; import "google/protobuf/empty.proto"; service Exporter { rpc SendContacts (SendContactsRequest) returns (google.protobuf.Empty); - rpc SendCase (SendCaseRequest) returns (google.protobuf.Empty); } message SendContactsRequest { repeated string emails = 1; } - -message SendCaseRequest { - string origin = 1; - string subject = 2; - string description = 3; - string contactEmail = 4; - string organization = 5; - string accountId = 6; - string rateLimitName = 7; - string rateLimitTier = 8; - string useCase = 9; -} - diff --git a/salesforce/email/proto/emailexporter_grpc.pb.go b/salesforce/email/proto/emailexporter_grpc.pb.go index 72e7c1b3a93..c7ef25e1be8 100644 --- a/salesforce/email/proto/emailexporter_grpc.pb.go +++ b/salesforce/email/proto/emailexporter_grpc.pb.go @@ -1,9 +1,3 @@ -// NOTE: This service is deprecated in favor of salesforce.Exporter. It must be -// kept in sync with salesforce.Exporter until we have fully migrated. -// -// TODO(#8410): Remove this service once we've fully migrated to -// salesforce.Exporter - // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: // - protoc-gen-go-grpc v1.5.1 @@ -27,7 +21,6 @@ const _ = grpc.SupportPackageIsVersion9 const ( Exporter_SendContacts_FullMethodName = "/email.Exporter/SendContacts" - Exporter_SendCase_FullMethodName = "/email.Exporter/SendCase" ) // ExporterClient is the client API for Exporter service. @@ -35,7 +28,6 @@ const ( // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type ExporterClient interface { SendContacts(ctx context.Context, in *SendContactsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) - SendCase(ctx context.Context, in *SendCaseRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) } type exporterClient struct { @@ -56,22 +48,11 @@ func (c *exporterClient) SendContacts(ctx context.Context, in *SendContactsReque return out, nil } -func (c *exporterClient) SendCase(ctx context.Context, in *SendCaseRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, Exporter_SendCase_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - // ExporterServer is the server API for Exporter service. // All implementations must embed UnimplementedExporterServer // for forward compatibility. type ExporterServer interface { SendContacts(context.Context, *SendContactsRequest) (*emptypb.Empty, error) - SendCase(context.Context, *SendCaseRequest) (*emptypb.Empty, error) mustEmbedUnimplementedExporterServer() } @@ -85,9 +66,6 @@ type UnimplementedExporterServer struct{} func (UnimplementedExporterServer) SendContacts(context.Context, *SendContactsRequest) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method SendContacts not implemented") } -func (UnimplementedExporterServer) SendCase(context.Context, *SendCaseRequest) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method SendCase not implemented") -} func (UnimplementedExporterServer) mustEmbedUnimplementedExporterServer() {} func (UnimplementedExporterServer) testEmbeddedByValue() {} @@ -127,24 +105,6 @@ func _Exporter_SendContacts_Handler(srv interface{}, ctx context.Context, dec fu return interceptor(ctx, in, info, handler) } -func _Exporter_SendCase_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(SendCaseRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ExporterServer).SendCase(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: Exporter_SendCase_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ExporterServer).SendCase(ctx, req.(*SendCaseRequest)) - } - return interceptor(ctx, in, info, handler) -} - // Exporter_ServiceDesc is the grpc.ServiceDesc for Exporter service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -156,10 +116,6 @@ var Exporter_ServiceDesc = grpc.ServiceDesc{ MethodName: "SendContacts", Handler: _Exporter_SendContacts_Handler, }, - { - MethodName: "SendCase", - Handler: _Exporter_SendCase_Handler, - }, }, Streams: []grpc.StreamDesc{}, Metadata: "emailexporter.proto", diff --git a/salesforce/exporter.go b/salesforce/exporter.go index 79c40054cc1..bb69b156106 100644 --- a/salesforce/exporter.go +++ b/salesforce/exporter.go @@ -13,7 +13,7 @@ import ( "github.com/letsencrypt/boulder/core" berrors "github.com/letsencrypt/boulder/errors" blog "github.com/letsencrypt/boulder/log" - salesforcepb "github.com/letsencrypt/boulder/salesforce/proto" + emailpb "github.com/letsencrypt/boulder/salesforce/email/proto" ) // contactsQueueCap limits the queue size to prevent unbounded growth. This @@ -25,7 +25,7 @@ var ErrQueueFull = errors.New("email-exporter queue is full") // ExporterImpl implements the gRPC server and processes email exports. type ExporterImpl struct { - salesforcepb.UnsafeExporterServer + emailpb.UnsafeExporterServer sync.Mutex drainWG sync.WaitGroup @@ -44,11 +44,10 @@ type ExporterImpl struct { emailCache *EmailCache emailsHandledCounter prometheus.Counter pardotErrorCounter prometheus.Counter - caseErrorCounter prometheus.Counter log blog.Logger } -var _ salesforcepb.ExporterServer = (*ExporterImpl)(nil) +var _ emailpb.ExporterServer = (*ExporterImpl)(nil) // NewExporterImpl initializes an ExporterImpl with the given client and // configuration. Both perDayLimit and maxConcurrentRequests should be @@ -70,11 +69,6 @@ func NewExporterImpl(client SalesforceClient, cache *EmailCache, perDayLimit flo Help: "Total number of Pardot API errors encountered by the email exporter", }) - caseErrorCounter := promauto.With(stats).NewCounter(prometheus.CounterOpts{ - Name: "email_exporter_case_errors", - Help: "Total number of errors encountered when sending Cases to the Salesforce REST API", - }) - impl := &ExporterImpl{ maxConcurrentRequests: maxConcurrentRequests, limiter: limiter, @@ -83,7 +77,6 @@ func NewExporterImpl(client SalesforceClient, cache *EmailCache, perDayLimit flo emailCache: cache, emailsHandledCounter: emailsHandledCounter, pardotErrorCounter: pardotErrorCounter, - caseErrorCounter: caseErrorCounter, log: logger, } impl.wake = sync.NewCond(&impl.Mutex) @@ -104,7 +97,7 @@ func NewExporterImpl(client SalesforceClient, cache *EmailCache, perDayLimit flo // SendContacts enqueues the provided email addresses. If the queue cannot // accommodate the new emails, an ErrQueueFull is returned. -func (impl *ExporterImpl) SendContacts(ctx context.Context, req *salesforcepb.SendContactsRequest) (*emptypb.Empty, error) { +func (impl *ExporterImpl) SendContacts(ctx context.Context, req *emailpb.SendContactsRequest) (*emptypb.Empty, error) { if core.IsAnyNilOrZero(req.Emails) { return nil, berrors.InternalServerError("Incomplete gRPC request message") } @@ -123,33 +116,6 @@ func (impl *ExporterImpl) SendContacts(ctx context.Context, req *salesforcepb.Se return &emptypb.Empty{}, nil } -// SendCase immediately submits a new Case to the Salesforce REST API using the -// provided details. Any retries are handled internally by the SalesforceClient. -// The following fields are required: Origin, Subject, ContactEmail. -func (impl *ExporterImpl) SendCase(ctx context.Context, req *salesforcepb.SendCaseRequest) (*emptypb.Empty, error) { - if core.IsAnyNilOrZero(req.Origin, req.Subject, req.ContactEmail) { - return nil, berrors.InternalServerError("incomplete gRPC request message") - } - - err := impl.client.SendCase(Case{ - Origin: req.Origin, - Subject: req.Subject, - Description: req.Description, - ContactEmail: req.ContactEmail, - Organization: req.Organization, - AccountId: req.AccountId, - RateLimitName: req.RateLimitName, - RateLimitTier: req.RateLimitTier, - UseCase: req.UseCase, - }) - if err != nil { - impl.caseErrorCounter.Inc() - return nil, berrors.InternalServerError("sending Case to the Salesforce REST API: %s", err) - } - - return &emptypb.Empty{}, nil -} - // Start begins asynchronous processing of the email queue. When the parent // daemonCtx is cancelled the queue will be drained and the workers will exit. func (impl *ExporterImpl) Start(daemonCtx context.Context) { diff --git a/salesforce/exporter_test.go b/salesforce/exporter_test.go index d7d818a8191..4cdf7993aba 100644 --- a/salesforce/exporter_test.go +++ b/salesforce/exporter_test.go @@ -10,7 +10,7 @@ import ( blog "github.com/letsencrypt/boulder/log" "github.com/letsencrypt/boulder/metrics" - salesforcepb "github.com/letsencrypt/boulder/salesforce/proto" + emailpb "github.com/letsencrypt/boulder/salesforce/email/proto" "github.com/letsencrypt/boulder/test" "github.com/prometheus/client_golang/prometheus" @@ -26,7 +26,6 @@ type mockSalesforceClientImpl struct { sync.Mutex CreatedContacts []string - CreatedCases []Case } // newMockSalesforceClientImpl returns a mockSalesforceClientImpl, which implements @@ -52,21 +51,6 @@ func (m *mockSalesforceClientImpl) getCreatedContacts() []string { return slices.Clone(m.CreatedContacts) } -func (m *mockSalesforceClientImpl) SendCase(payload Case) error { - m.Lock() - defer m.Unlock() - m.CreatedCases = append(m.CreatedCases, payload) - return nil -} - -func (m *mockSalesforceClientImpl) getCreatedCases() []Case { - m.Lock() - defer m.Unlock() - - // Return a copy to avoid race conditions. - return slices.Clone(m.CreatedCases) -} - // setup creates a new ExporterImpl, a mockSalesForceClientImpl, and the start and // cleanup functions for the ExporterImpl. Call start() to begin processing the // ExporterImpl queue and cleanup() to drain and shutdown. If start() is called, @@ -91,7 +75,7 @@ func TestSendContacts(t *testing.T) { defer cleanup() wantContacts := []string{"test@example.com", "user@example.com"} - _, err := exporter.SendContacts(ctx, &salesforcepb.SendContactsRequest{ + _, err := exporter.SendContacts(ctx, &emailpb.SendContactsRequest{ Emails: wantContacts, }) test.AssertNotError(t, err, "Error creating contacts") @@ -120,7 +104,7 @@ func TestSendContactsQueueFull(t *testing.T) { var err error for range contactsQueueCap * 2 { - _, err = exporter.SendContacts(ctx, &salesforcepb.SendContactsRequest{ + _, err = exporter.SendContacts(ctx, &emailpb.SendContactsRequest{ Emails: []string{"test@example.com"}, }) if err != nil { @@ -141,7 +125,7 @@ func TestSendContactsQueueDrains(t *testing.T) { emails = append(emails, fmt.Sprintf("test@%d.example.com", i)) } - _, err := exporter.SendContacts(ctx, &salesforcepb.SendContactsRequest{ + _, err := exporter.SendContacts(ctx, &emailpb.SendContactsRequest{ Emails: emails, }) test.AssertNotError(t, err, "Error creating contacts") @@ -169,7 +153,7 @@ func TestSendContactsErrorMetrics(t *testing.T) { daemonCtx, cancel := context.WithCancel(context.Background()) exporter.Start(daemonCtx) - _, err := exporter.SendContacts(ctx, &salesforcepb.SendContactsRequest{ + _, err := exporter.SendContacts(ctx, &emailpb.SendContactsRequest{ Emails: []string{"test@example.com"}, }) test.AssertNotError(t, err, "Error creating contacts") @@ -192,7 +176,7 @@ func TestSendContactDeduplication(t *testing.T) { daemonCtx, cancel := context.WithCancel(context.Background()) exporter.Start(daemonCtx) - _, err := exporter.SendContacts(ctx, &salesforcepb.SendContactsRequest{ + _, err := exporter.SendContacts(ctx, &emailpb.SendContactsRequest{ Emails: []string{"duplicate@example.com", "duplicate@example.com"}, }) test.AssertNotError(t, err, "Error enqueuing contacts") @@ -224,7 +208,7 @@ func TestSendContactErrorRemovesFromCache(t *testing.T) { daemonCtx, cancel := context.WithCancel(context.Background()) exporter.Start(daemonCtx) - _, err := exporter.SendContacts(ctx, &salesforcepb.SendContactsRequest{ + _, err := exporter.SendContacts(ctx, &emailpb.SendContactsRequest{ Emails: []string{"error@example.com"}, }) test.AssertNotError(t, err, "enqueue failed") @@ -242,68 +226,3 @@ func TestSendContactErrorRemovesFromCache(t *testing.T) { // Check that the error counter was incremented. test.AssertMetricWithLabelsEquals(t, exporter.pardotErrorCounter, prometheus.Labels{}, 1) } - -func TestSendCase(t *testing.T) { - t.Parallel() - - clientImpl := newMockSalesforceClientImpl() - exporter := NewExporterImpl(clientImpl, nil, 1000000, 5, metrics.NoopRegisterer, blog.NewMock()) - - _, err := exporter.SendCase(ctx, &salesforcepb.SendCaseRequest{ - Origin: "Web", - Subject: "Some Override", - Description: "Please review", - ContactEmail: "foo@example.com", - }) - test.AssertNotError(t, err, "SendCase should succeed") - - got := clientImpl.getCreatedCases() - if len(got) != 1 { - t.Fatalf("expected 1 case, got %d", len(got)) - } - test.AssertEquals(t, got[0].Origin, "Web") - test.AssertEquals(t, got[0].Subject, "Some Override") - test.AssertEquals(t, got[0].Description, "Please review") - test.AssertEquals(t, got[0].ContactEmail, "foo@example.com") - test.AssertMetricWithLabelsEquals(t, exporter.caseErrorCounter, prometheus.Labels{}, 0) -} - -type mockAlwaysFailCaseClient struct { - mockSalesforceClientImpl -} - -func (m *mockAlwaysFailCaseClient) SendCase(payload Case) error { - return fmt.Errorf("oops, lol") -} - -func TestSendCaseClientErrorIncrementsMetric(t *testing.T) { - t.Parallel() - - mockClient := &mockAlwaysFailCaseClient{} - exporter := NewExporterImpl(mockClient, nil, 1000000, 5, metrics.NoopRegisterer, blog.NewMock()) - - _, err := exporter.SendCase(ctx, &salesforcepb.SendCaseRequest{ - Origin: "Web", - Subject: "Some Override", - Description: "Please review", - ContactEmail: "foo@bar.baz", - }) - test.AssertError(t, err, "SendCase should return error on client failure") - test.AssertMetricWithLabelsEquals(t, exporter.caseErrorCounter, prometheus.Labels{}, 1) -} - -func TestSendCaseMissingOriginValidation(t *testing.T) { - t.Parallel() - - clientImpl := newMockSalesforceClientImpl() - exporter := NewExporterImpl(clientImpl, nil, 1000000, 5, metrics.NoopRegisterer, blog.NewMock()) - - _, err := exporter.SendCase(ctx, &salesforcepb.SendCaseRequest{Subject: "No origin in this one, d00d"}) - test.AssertError(t, err, "SendCase should fail validation when Origin is missing") - - got := clientImpl.getCreatedCases() - if len(got) != 0 { - t.Errorf("expected 0 cases due to validation error, got %d", len(got)) - } - test.AssertMetricWithLabelsEquals(t, exporter.caseErrorCounter, prometheus.Labels{}, 0) -} diff --git a/salesforce/pardot.go b/salesforce/pardot.go index 06eaf4f215e..f3852ef3475 100644 --- a/salesforce/pardot.go +++ b/salesforce/pardot.go @@ -25,16 +25,6 @@ const ( // https://developer.salesforce.com/docs/marketing/pardot/guide/prospect-v5.html#prospect-upsert-by-email contactsPath = "/api/v5/objects/prospects/do/upsertLatestByEmail" - // casesPath is the path to create a new Case object in Salesforce. This - // path includes the API version (v64.0). Normally, Salesforce maintains - // backward compatibility across versions. Update only if Salesforce retires - // this API version (rare) or we want to make use of new Case fields - // (unlikely). - // - // To check the current version for our org, see "Identify your current API - // version": https://help.salesforce.com/s/articleView?id=000386929&type=1 - casesPath = "/services/data/v64.0/sobjects/Case" - // maxAttempts is the maximum number of attempts to retry a request. maxAttempts = 3 @@ -56,7 +46,6 @@ const ( // Salesforce APIs. It exists to facilitate testing mocks. type SalesforceClient interface { SendContact(email string) error - SendCase(payload Case) error } // oAuthToken holds the OAuth2 access token and its expiration. @@ -67,14 +56,12 @@ type oAuthToken struct { expiresAt time.Time } -// SalesforceClientImpl handles authentication and sending contacts to Pardot -// and creating Cases in Salesforce. +// SalesforceClientImpl handles authentication and sending contacts to Pardot. type SalesforceClientImpl struct { businessUnit string clientId string clientSecret string pardotURL string - casesURL string tokenURL string token *oAuthToken clk clock.Clock @@ -92,17 +79,12 @@ func NewSalesforceClientImpl(clk clock.Clock, businessUnit, clientId, clientSecr if err != nil { return nil, fmt.Errorf("failed to join token path: %w", err) } - casesURL, err := url.JoinPath(salesforceBaseURL, casesPath) - if err != nil { - return nil, fmt.Errorf("failed to join cases path: %w", err) - } return &SalesforceClientImpl{ businessUnit: businessUnit, clientId: clientId, clientSecret: clientSecret, pardotURL: pardotURL, - casesURL: casesURL, tokenURL: tokenURL, token: &oAuthToken{}, clk: clk, @@ -233,102 +215,3 @@ func (pc *SalesforceClientImpl) SendContact(email string) error { return finalErr } - -// Case represents the payload for populating a new Case object in Salesforce. -// For more information, see: -// https://developer.salesforce.com/docs/atlas.en-us.object_reference.meta/object_reference/sforce_api_objects_case.htm -// https://help.salesforce.com/s/articleView?id=platform.custom_field_types.htm&type=5 -type Case struct { - // Origin is required in all requests, a safe default is "Web". - Origin string `json:"Origin"` - - // Subject is an optional standard field. Max length: 255 characters. - Subject string `json:"Subject,omitempty"` - - // Description is an optional standard field. Max length: 32,768 characters. - Description string `json:"Description,omitempty"` - - // ContactEmail is an optional standard field indicating the email address - // of the requester. Max length: 80 characters. - ContactEmail string `json:"ContactEmail,omitempty"` - - // Note: Fields below this point are optional custom fields. - - // Organization indicates the name of the requesting organization. Max - // length: 255 characters. - Organization string `json:"Organization__c,omitempty"` - - // AccountId indicates the requester's ACME Account ID. Max length: 255 - // characters. - AccountId string `json:"Account_ID__c,omitempty"` - - // RateLimitName indicates which rate limit the override request is for. Max - // length: 255 characters. - RateLimitName string `json:"Rate_Limit_Name__c,omitempty"` - - // Tier indicates the requested tier of the rate limit override. Max length: - // 255 characters. - RateLimitTier string `json:"Rate_Limit_Tier__c,omitempty"` - - // UseCase indicates the intended to use case supplied by the requester. Max - // length: 131,072 characters. - UseCase string `json:"Use_Case__c,omitempty"` -} - -// SendCase submits a new Case object to Salesforce. For more information, see: -// https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/dome_sobject_create.htm -func (pc *SalesforceClientImpl) SendCase(payload Case) error { - var err error - for attempt := range maxAttempts { - time.Sleep(core.RetryBackoff(attempt, retryBackoffMin, retryBackoffMax, retryBackoffBase)) - err = pc.updateToken() - if err == nil { - break - } - } - if err != nil { - return fmt.Errorf("failed to update token: %w", err) - } - - body, err := json.Marshal(payload) - if err != nil { - return fmt.Errorf("failed to marshal case payload: %w", err) - } - - var finalErr error - for attempt := range maxAttempts { - time.Sleep(core.RetryBackoff(attempt, retryBackoffMin, retryBackoffMax, retryBackoffBase)) - - req, err := http.NewRequest("POST", pc.casesURL, bytes.NewReader(body)) - if err != nil { - finalErr = fmt.Errorf("failed to create new case request: %w", err) - continue - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", "Bearer "+pc.token.accessToken) - - resp, err := http.DefaultClient.Do(req) - if err != nil { - finalErr = fmt.Errorf("create case request failed: %w", err) - continue - } - - if resp.StatusCode >= 200 && resp.StatusCode < 300 { - resp.Body.Close() - return nil - } - - respBody, err := io.ReadAll(resp.Body) - resp.Body.Close() - - if err != nil { - finalErr = fmt.Errorf("create case request returned status %d; while reading body: %w", resp.StatusCode, err) - continue - } - - finalErr = fmt.Errorf("create case request returned status %d: %s", resp.StatusCode, redactEmail(respBody, payload.ContactEmail)) - continue - } - - return finalErr -} diff --git a/salesforce/pardot_test.go b/salesforce/pardot_test.go index 11a4648a461..7407b1d5265 100644 --- a/salesforce/pardot_test.go +++ b/salesforce/pardot_test.go @@ -209,120 +209,3 @@ func TestSendContactRedactsEmail(t *testing.T) { test.AssertNotContains(t, err.Error(), emailToTest) test.AssertContains(t, err.Error(), "[REDACTED]") } - -func TestSendCaseSuccess(t *testing.T) { - t.Parallel() - - handler := func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { - case "/services/oauth2/token": - defaultTokenHandler(w, r) - case "/services/data/v64.0/sobjects/Case": - if r.Header.Get("Authorization") != "Bearer dummy" { - w.WriteHeader(http.StatusUnauthorized) - return - } - w.WriteHeader(http.StatusCreated) - w.Write([]byte(`{"id":"500xx000001ABCdAAH","success":true,"errors":[]}`)) - default: - t.Errorf("unexpected path: %s", r.URL.Path) - w.WriteHeader(http.StatusNotFound) - } - } - - salesforceSrv := httptest.NewServer(http.HandlerFunc(handler)) - defer salesforceSrv.Close() - - client, err := NewSalesforceClientImpl(clock.NewFake(), "biz-unit", "cid", "csec", salesforceSrv.URL, "") - test.AssertNotError(t, err, "failed to create client") - - err = client.SendCase(Case{ - Subject: "Unit Test Case", - Origin: "Web", - }) - test.AssertNotError(t, err, "SendCase should succeed") -} - -func TestSendCaseUpdateTokenFails(t *testing.T) { - t.Parallel() - - handler := func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { - case "/services/oauth2/token": - w.WriteHeader(http.StatusInternalServerError) - fmt.Fprintln(w, "token error") - case "/services/data/v64.0/sobjects/Case": - w.WriteHeader(http.StatusOK) // should never reach here - default: - t.Errorf("unexpected path: %s", r.URL.Path) - w.WriteHeader(http.StatusNotFound) - } - } - - salesforceSrv := httptest.NewServer(http.HandlerFunc(handler)) - defer salesforceSrv.Close() - - client, err := NewSalesforceClientImpl(clock.NewFake(), "biz-unit", "cid", "csec", salesforceSrv.URL, "") - test.AssertNotError(t, err, "Failed to create client") - - err = client.SendCase(Case{Subject: "fail", Origin: "Web"}) - test.AssertError(t, err, "Expected token update to fail") - test.AssertContains(t, err.Error(), "failed to update token") -} - -func TestSendCase4xx(t *testing.T) { - t.Parallel() - - handler := func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { - case "/services/oauth2/token": - defaultTokenHandler(w, r) - case "/services/data/v64.0/sobjects/Case": - w.WriteHeader(http.StatusBadRequest) - _, err := io.WriteString(w, "bad request") - test.AssertNotError(t, err, "failed to write response") - default: - t.Errorf("unexpected path: %s", r.URL.Path) - w.WriteHeader(http.StatusNotFound) - } - } - - salesforceSrv := httptest.NewServer(http.HandlerFunc(handler)) - defer salesforceSrv.Close() - - client, err := NewSalesforceClientImpl(clock.NewFake(), "biz-unit", "cid", "csec", salesforceSrv.URL, "") - test.AssertNotError(t, err, "Failed to create client") - - err = client.SendCase(Case{Subject: "bad", Origin: "Web"}) - test.AssertError(t, err, "Should fail on 400") - test.AssertContains(t, err.Error(), "create case request returned status 400") -} - -func TestSendCaseServerErrorsAfterMaxAttempts(t *testing.T) { - t.Parallel() - - gotAttempts := 0 - handler := func(w http.ResponseWriter, r *http.Request) { - switch r.URL.Path { - case "/services/oauth2/token": - defaultTokenHandler(w, r) - case "/services/data/v64.0/sobjects/Case": - gotAttempts++ - w.WriteHeader(http.StatusServiceUnavailable) - default: - t.Errorf("unexpected path: %s", r.URL.Path) - w.WriteHeader(http.StatusNotFound) - } - } - - salesforceSrv := httptest.NewServer(http.HandlerFunc(handler)) - defer salesforceSrv.Close() - - client, err := NewSalesforceClientImpl(clock.NewFake(), "biz-unit", "cid", "csec", salesforceSrv.URL, "") - test.AssertNotError(t, err, "Failed to create client") - - err = client.SendCase(Case{Subject: "retry", Origin: "Web"}) - test.AssertError(t, err, "Should fail after retrying all attempts") - test.AssertEquals(t, maxAttempts, gotAttempts) - test.AssertContains(t, err.Error(), "create case request returned status 503") -} diff --git a/salesforce/proto/exporter.pb.go b/salesforce/proto/exporter.pb.go deleted file mode 100644 index 777fd754ff0..00000000000 --- a/salesforce/proto/exporter.pb.go +++ /dev/null @@ -1,279 +0,0 @@ -// NOTE: Any changes to this service MUST also be made to email.Exporter and -// kept in sync with the adapter in cmd/email-exporter/main.go. -// -// TODO(#8410): Remove this comment once we've fully migrated to -// salesforce.Exporter. - -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.36.5 -// protoc v3.20.1 -// source: exporter.proto - -package proto - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - emptypb "google.golang.org/protobuf/types/known/emptypb" - reflect "reflect" - sync "sync" - unsafe "unsafe" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type SendContactsRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Emails []string `protobuf:"bytes,1,rep,name=emails,proto3" json:"emails,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *SendContactsRequest) Reset() { - *x = SendContactsRequest{} - mi := &file_exporter_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *SendContactsRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SendContactsRequest) ProtoMessage() {} - -func (x *SendContactsRequest) ProtoReflect() protoreflect.Message { - mi := &file_exporter_proto_msgTypes[0] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SendContactsRequest.ProtoReflect.Descriptor instead. -func (*SendContactsRequest) Descriptor() ([]byte, []int) { - return file_exporter_proto_rawDescGZIP(), []int{0} -} - -func (x *SendContactsRequest) GetEmails() []string { - if x != nil { - return x.Emails - } - return nil -} - -type SendCaseRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - Origin string `protobuf:"bytes,1,opt,name=origin,proto3" json:"origin,omitempty"` - Subject string `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"` - Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` - ContactEmail string `protobuf:"bytes,4,opt,name=contactEmail,proto3" json:"contactEmail,omitempty"` - Organization string `protobuf:"bytes,5,opt,name=organization,proto3" json:"organization,omitempty"` - AccountId string `protobuf:"bytes,6,opt,name=accountId,proto3" json:"accountId,omitempty"` - RateLimitName string `protobuf:"bytes,7,opt,name=rateLimitName,proto3" json:"rateLimitName,omitempty"` - RateLimitTier string `protobuf:"bytes,8,opt,name=rateLimitTier,proto3" json:"rateLimitTier,omitempty"` - UseCase string `protobuf:"bytes,9,opt,name=useCase,proto3" json:"useCase,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache -} - -func (x *SendCaseRequest) Reset() { - *x = SendCaseRequest{} - mi := &file_exporter_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) -} - -func (x *SendCaseRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*SendCaseRequest) ProtoMessage() {} - -func (x *SendCaseRequest) ProtoReflect() protoreflect.Message { - mi := &file_exporter_proto_msgTypes[1] - if x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use SendCaseRequest.ProtoReflect.Descriptor instead. -func (*SendCaseRequest) Descriptor() ([]byte, []int) { - return file_exporter_proto_rawDescGZIP(), []int{1} -} - -func (x *SendCaseRequest) GetOrigin() string { - if x != nil { - return x.Origin - } - return "" -} - -func (x *SendCaseRequest) GetSubject() string { - if x != nil { - return x.Subject - } - return "" -} - -func (x *SendCaseRequest) GetDescription() string { - if x != nil { - return x.Description - } - return "" -} - -func (x *SendCaseRequest) GetContactEmail() string { - if x != nil { - return x.ContactEmail - } - return "" -} - -func (x *SendCaseRequest) GetOrganization() string { - if x != nil { - return x.Organization - } - return "" -} - -func (x *SendCaseRequest) GetAccountId() string { - if x != nil { - return x.AccountId - } - return "" -} - -func (x *SendCaseRequest) GetRateLimitName() string { - if x != nil { - return x.RateLimitName - } - return "" -} - -func (x *SendCaseRequest) GetRateLimitTier() string { - if x != nil { - return x.RateLimitTier - } - return "" -} - -func (x *SendCaseRequest) GetUseCase() string { - if x != nil { - return x.UseCase - } - return "" -} - -var File_exporter_proto protoreflect.FileDescriptor - -var file_exporter_proto_rawDesc = string([]byte{ - 0x0a, 0x0e, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x12, 0x0a, 0x73, 0x61, 0x6c, 0x65, 0x73, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x1a, 0x1b, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, - 0x70, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x2d, 0x0a, 0x13, 0x53, 0x65, 0x6e, - 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x12, 0x16, 0x0a, 0x06, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x06, 0x65, 0x6d, 0x61, 0x69, 0x6c, 0x73, 0x22, 0xb1, 0x02, 0x0a, 0x0f, 0x53, 0x65, 0x6e, - 0x64, 0x43, 0x61, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, - 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x72, - 0x69, 0x67, 0x69, 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x20, - 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x22, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x45, 0x6d, 0x61, 0x69, 0x6c, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x45, - 0x6d, 0x61, 0x69, 0x6c, 0x12, 0x22, 0x0a, 0x0c, 0x6f, 0x72, 0x67, 0x61, 0x6e, 0x69, 0x7a, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x6f, 0x72, 0x67, 0x61, - 0x6e, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x63, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x49, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x63, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x49, 0x64, 0x12, 0x24, 0x0a, 0x0d, 0x72, 0x61, 0x74, 0x65, 0x4c, 0x69, - 0x6d, 0x69, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, - 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x24, 0x0a, 0x0d, - 0x72, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x54, 0x69, 0x65, 0x72, 0x18, 0x08, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x0d, 0x72, 0x61, 0x74, 0x65, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x54, 0x69, - 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x75, 0x73, 0x65, 0x43, 0x61, 0x73, 0x65, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x07, 0x75, 0x73, 0x65, 0x43, 0x61, 0x73, 0x65, 0x32, 0x94, 0x01, 0x0a, - 0x08, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x72, 0x12, 0x47, 0x0a, 0x0c, 0x53, 0x65, 0x6e, - 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x63, 0x74, 0x73, 0x12, 0x1f, 0x2e, 0x73, 0x61, 0x6c, 0x65, - 0x73, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6e, 0x74, 0x61, - 0x63, 0x74, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, - 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, - 0x74, 0x79, 0x12, 0x3f, 0x0a, 0x08, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x61, 0x73, 0x65, 0x12, 0x1b, - 0x2e, 0x73, 0x61, 0x6c, 0x65, 0x73, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x2e, 0x53, 0x65, 0x6e, 0x64, - 0x43, 0x61, 0x73, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x42, 0x31, 0x5a, 0x2f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, - 0x6d, 0x2f, 0x6c, 0x65, 0x74, 0x73, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2f, 0x62, 0x6f, - 0x75, 0x6c, 0x64, 0x65, 0x72, 0x2f, 0x73, 0x61, 0x6c, 0x65, 0x73, 0x66, 0x6f, 0x72, 0x63, 0x65, - 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -}) - -var ( - file_exporter_proto_rawDescOnce sync.Once - file_exporter_proto_rawDescData []byte -) - -func file_exporter_proto_rawDescGZIP() []byte { - file_exporter_proto_rawDescOnce.Do(func() { - file_exporter_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_exporter_proto_rawDesc), len(file_exporter_proto_rawDesc))) - }) - return file_exporter_proto_rawDescData -} - -var file_exporter_proto_msgTypes = make([]protoimpl.MessageInfo, 2) -var file_exporter_proto_goTypes = []any{ - (*SendContactsRequest)(nil), // 0: salesforce.SendContactsRequest - (*SendCaseRequest)(nil), // 1: salesforce.SendCaseRequest - (*emptypb.Empty)(nil), // 2: google.protobuf.Empty -} -var file_exporter_proto_depIdxs = []int32{ - 0, // 0: salesforce.Exporter.SendContacts:input_type -> salesforce.SendContactsRequest - 1, // 1: salesforce.Exporter.SendCase:input_type -> salesforce.SendCaseRequest - 2, // 2: salesforce.Exporter.SendContacts:output_type -> google.protobuf.Empty - 2, // 3: salesforce.Exporter.SendCase:output_type -> google.protobuf.Empty - 2, // [2:4] is the sub-list for method output_type - 0, // [0:2] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name -} - -func init() { file_exporter_proto_init() } -func file_exporter_proto_init() { - if File_exporter_proto != nil { - return - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: unsafe.Slice(unsafe.StringData(file_exporter_proto_rawDesc), len(file_exporter_proto_rawDesc)), - NumEnums: 0, - NumMessages: 2, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_exporter_proto_goTypes, - DependencyIndexes: file_exporter_proto_depIdxs, - MessageInfos: file_exporter_proto_msgTypes, - }.Build() - File_exporter_proto = out.File - file_exporter_proto_goTypes = nil - file_exporter_proto_depIdxs = nil -} diff --git a/salesforce/proto/exporter.proto b/salesforce/proto/exporter.proto deleted file mode 100644 index 55e04dfdda2..00000000000 --- a/salesforce/proto/exporter.proto +++ /dev/null @@ -1,34 +0,0 @@ -// NOTE: Any changes to this service MUST also be made to email.Exporter and -// kept in sync with the adapter in cmd/email-exporter/main.go. -// -// TODO(#8410): Remove this comment once we've fully migrated to -// salesforce.Exporter. - -syntax = "proto3"; - -package salesforce; -option go_package = "github.com/letsencrypt/boulder/salesforce/proto"; - -import "google/protobuf/empty.proto"; - -service Exporter { - rpc SendContacts (SendContactsRequest) returns (google.protobuf.Empty); - rpc SendCase (SendCaseRequest) returns (google.protobuf.Empty); -} - -message SendContactsRequest { - repeated string emails = 1; -} - -message SendCaseRequest { - string origin = 1; - string subject = 2; - string description = 3; - string contactEmail = 4; - string organization = 5; - string accountId = 6; - string rateLimitName = 7; - string rateLimitTier = 8; - string useCase = 9; -} - diff --git a/salesforce/proto/exporter_grpc.pb.go b/salesforce/proto/exporter_grpc.pb.go deleted file mode 100644 index 5758e32ebf6..00000000000 --- a/salesforce/proto/exporter_grpc.pb.go +++ /dev/null @@ -1,166 +0,0 @@ -// NOTE: Any changes to this service MUST also be made to email.Exporter and -// kept in sync with the adapter in cmd/email-exporter/main.go. -// -// TODO(#8410): Remove this comment once we've fully migrated to -// salesforce.Exporter. - -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.5.1 -// - protoc v3.20.1 -// source: exporter.proto - -package proto - -import ( - context "context" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" - emptypb "google.golang.org/protobuf/types/known/emptypb" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.64.0 or later. -const _ = grpc.SupportPackageIsVersion9 - -const ( - Exporter_SendContacts_FullMethodName = "/salesforce.Exporter/SendContacts" - Exporter_SendCase_FullMethodName = "/salesforce.Exporter/SendCase" -) - -// ExporterClient is the client API for Exporter service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type ExporterClient interface { - SendContacts(ctx context.Context, in *SendContactsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) - SendCase(ctx context.Context, in *SendCaseRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) -} - -type exporterClient struct { - cc grpc.ClientConnInterface -} - -func NewExporterClient(cc grpc.ClientConnInterface) ExporterClient { - return &exporterClient{cc} -} - -func (c *exporterClient) SendContacts(ctx context.Context, in *SendContactsRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, Exporter_SendContacts_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *exporterClient) SendCase(ctx context.Context, in *SendCaseRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { - cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) - out := new(emptypb.Empty) - err := c.cc.Invoke(ctx, Exporter_SendCase_FullMethodName, in, out, cOpts...) - if err != nil { - return nil, err - } - return out, nil -} - -// ExporterServer is the server API for Exporter service. -// All implementations must embed UnimplementedExporterServer -// for forward compatibility. -type ExporterServer interface { - SendContacts(context.Context, *SendContactsRequest) (*emptypb.Empty, error) - SendCase(context.Context, *SendCaseRequest) (*emptypb.Empty, error) - mustEmbedUnimplementedExporterServer() -} - -// UnimplementedExporterServer must be embedded to have -// forward compatible implementations. -// -// NOTE: this should be embedded by value instead of pointer to avoid a nil -// pointer dereference when methods are called. -type UnimplementedExporterServer struct{} - -func (UnimplementedExporterServer) SendContacts(context.Context, *SendContactsRequest) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method SendContacts not implemented") -} -func (UnimplementedExporterServer) SendCase(context.Context, *SendCaseRequest) (*emptypb.Empty, error) { - return nil, status.Errorf(codes.Unimplemented, "method SendCase not implemented") -} -func (UnimplementedExporterServer) mustEmbedUnimplementedExporterServer() {} -func (UnimplementedExporterServer) testEmbeddedByValue() {} - -// UnsafeExporterServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to ExporterServer will -// result in compilation errors. -type UnsafeExporterServer interface { - mustEmbedUnimplementedExporterServer() -} - -func RegisterExporterServer(s grpc.ServiceRegistrar, srv ExporterServer) { - // If the following call pancis, it indicates UnimplementedExporterServer was - // embedded by pointer and is nil. This will cause panics if an - // unimplemented method is ever invoked, so we test this at initialization - // time to prevent it from happening at runtime later due to I/O. - if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { - t.testEmbeddedByValue() - } - s.RegisterService(&Exporter_ServiceDesc, srv) -} - -func _Exporter_SendContacts_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(SendContactsRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ExporterServer).SendContacts(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: Exporter_SendContacts_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ExporterServer).SendContacts(ctx, req.(*SendContactsRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _Exporter_SendCase_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(SendCaseRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ExporterServer).SendCase(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: Exporter_SendCase_FullMethodName, - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ExporterServer).SendCase(ctx, req.(*SendCaseRequest)) - } - return interceptor(ctx, in, info, handler) -} - -// Exporter_ServiceDesc is the grpc.ServiceDesc for Exporter service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var Exporter_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "salesforce.Exporter", - HandlerType: (*ExporterServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "SendContacts", - Handler: _Exporter_SendContacts_Handler, - }, - { - MethodName: "SendCase", - Handler: _Exporter_SendCase_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "exporter.proto", -} diff --git a/sfe/overrides.go b/sfe/overrides.go index 61ef8648175..d4c47609615 100644 --- a/sfe/overrides.go +++ b/sfe/overrides.go @@ -17,7 +17,7 @@ import ( "github.com/letsencrypt/boulder/policy" rapb "github.com/letsencrypt/boulder/ra/proto" rl "github.com/letsencrypt/boulder/ratelimits" - salesforcepb "github.com/letsencrypt/boulder/salesforce/proto" + emailpb "github.com/letsencrypt/boulder/salesforce/email/proto" "github.com/letsencrypt/boulder/sfe/forms" "github.com/letsencrypt/boulder/sfe/zendesk" "github.com/letsencrypt/boulder/web" @@ -43,7 +43,6 @@ const ( privacyPolicyFieldName = "privacyPolicy" emailAddressFieldName = "emailAddress" useCaseFieldName = "useCase" - fundraisingFieldName = "fundraising" mailingListFieldName = "mailingList" // reviewStatusDefault is the initial status of a ticket when created. @@ -94,14 +93,6 @@ var ( // the CertificatesPerDomainPerAccount rate limit override requests. certificatesPerDomainPerAccountTierOptions = []string{"300", "1000", "5000", "10000", "25000", "50000", "75000", "100000", "175000", "250000", "500000", "1000000", "1750000", "2500000"} - fundraisingYesOption = "Yes, email me more information." - - // FundraisingOptions is the list of options for the fundraising field. - FundraisingOptions = []string{ - fundraisingYesOption, - "No, not at this time.", - } - // tierOptionsByRateLimit maps rate limit names to their valid tiers. tierOptionsByRateLimit = map[string][]string{ rl.NewOrdersPerAccount.String(): newOrdersPerAccountTierOptions, @@ -110,19 +101,6 @@ var ( rl.CertificatesPerDomainPerAccount.String(): certificatesPerDomainPerAccountTierOptions, } - fundraisingField = forms.NewDropdownField( - "Did you know that Let's Encrypt is a non-profit project?", - fundraisingFieldName, - `Funding for Let's Encrypt comes from contributions from our community -of users and advocates. While financially supporting Let's Encrypt is completely -optional and not required to use the service, we depend on the generosity of users -like you. - -Would your organization consider financially supporting Let's Encrypt as a Sponsor?`, - FundraisingOptions, - true, - ) - baseFields = []forms.Field{ forms.NewCheckboxField( "Subscriber Agreement", @@ -140,9 +118,9 @@ subject to its terms.`, will be processed in accordance with Let's Encrypt's Privacy Policy. I understand that ISRG collects and will process this information to evaluate my rate limit override request and to -provide certificate issuance and management services. In addition, depending on my -responses to questions below, ISRG may use this information to send me email updates -and sponsorship information.`, +provide certificate issuance and management services. In addition, depending on my +responses to questions below, ISRG may use this information to send me email +updates.`, true, ), forms.NewCheckboxField( @@ -176,10 +154,9 @@ contact person if needed.`, ) // overridesForm creates a new form with the base fields and the provided custom -// fields. The custom fields will appear after the baseFields and before the -// fundraising field. +// fields. The custom fields will appear after the baseFields. func overridesForm(customFields ...forms.Field) *forms.Form { - return forms.NewForm(append(append(baseFields, customFields...), fundraisingField)...) + return forms.NewForm(append(baseFields, customFields...)...) } var ( @@ -341,12 +318,6 @@ func validateOverrideRequestField(fieldName, fieldValue, rateLimit string) error } return nil - case fundraisingFieldName: - if !slices.Contains(FundraisingOptions, fieldValue) { - return fmt.Errorf("invalid fundraising option, valid options are: %s", strings.Join(FundraisingOptions, ", ")) - } - return nil - case emailAddressFieldName: err := policy.ValidEmail(fieldValue) if err == nil { @@ -638,7 +609,6 @@ func (sfe *SelfServiceFrontEndImpl) submitOverrideRequestHandler(w http.Response subscriberAgreementFieldName, privacyPolicyFieldName, mailingListFieldName, - fundraisingFieldName, emailAddressFieldName, OrganizationFieldName, useCaseFieldName, @@ -725,27 +695,12 @@ func (sfe *SelfServiceFrontEndImpl) submitOverrideRequestHandler(w http.Response } if sfe.ee != nil && validFields[mailingListFieldName] == "true" { - _, err := sfe.ee.SendContacts(r.Context(), &salesforcepb.SendContactsRequest{Emails: []string{validFields[emailAddressFieldName]}}) + _, err := sfe.ee.SendContacts(r.Context(), &emailpb.SendContactsRequest{Emails: []string{validFields[emailAddressFieldName]}}) if err != nil { sfe.log.Errf("failed to send contact to email-exporter: %s", err) } } - if sfe.ee != nil && validFields[fundraisingFieldName] == fundraisingYesOption { - _, err := sfe.ee.SendCase(r.Context(), &salesforcepb.SendCaseRequest{ - Origin: "Web", - Subject: fmt.Sprintf("%s rate limit override request for %s", req.RateLimit, validFields[OrganizationFieldName]), - ContactEmail: validFields[emailAddressFieldName], - Organization: validFields[OrganizationFieldName], - RateLimitName: req.RateLimit, - RateLimitTier: validFields[TierFieldName], - UseCase: validFields[useCaseFieldName], - }) - if err != nil { - sfe.log.Errf("failed to send case to email-exporter: %s", err) - } - } - if requestHandled { sfe.log.Infof("automatically approved override request for %s", validFields[OrganizationFieldName]) w.WriteHeader(http.StatusCreated) diff --git a/sfe/overrides_test.go b/sfe/overrides_test.go index 111ab4f63f4..e14a40d97eb 100644 --- a/sfe/overrides_test.go +++ b/sfe/overrides_test.go @@ -166,7 +166,6 @@ func TestSubmitOverrideRequestHandlerErrors(t *testing.T) { subscriberAgreementFieldName: "true", privacyPolicyFieldName: "true", mailingListFieldName: "false", - fundraisingFieldName: FundraisingOptions[0], emailAddressFieldName: "foo@bar.co", OrganizationFieldName: "Big Host Inc.", useCaseFieldName: strings.Repeat("x", 60), @@ -187,9 +186,6 @@ func TestSubmitOverrideRequestHandlerErrors(t *testing.T) { if len(mockImpl.GetCreatedContacts()) != 0 { t.Errorf("PardotClient.SendContact called unexpectedly") } - if len(mockImpl.GetCreatedCases()) != 0 { - t.Errorf("PardotClient.SendCase called unexpectedly") - } } func TestSubmitOverrideRequestHandlerSuccess(t *testing.T) { @@ -205,7 +201,6 @@ func TestSubmitOverrideRequestHandlerSuccess(t *testing.T) { subscriberAgreementFieldName: "true", privacyPolicyFieldName: "true", mailingListFieldName: "true", - fundraisingFieldName: FundraisingOptions[0], emailAddressFieldName: "foo@bar.co", OrganizationFieldName: "Big Host Inc.", useCaseFieldName: strings.Repeat("x", 60), @@ -328,9 +323,6 @@ func TestSubmitOverrideRequestHandlerSuccess(t *testing.T) { if len(mockImpl.GetCreatedContacts()) != 1 { t.Errorf("PardotClient.SendContact not called exactly once") } - if len(mockImpl.GetCreatedCases()) != 1 { - t.Errorf("PardotClient.SendCase not called exactly once") - } }) } } @@ -367,11 +359,6 @@ func TestValidateOverrideRequestField(t *testing.T) { testCase{fieldName + " yep", fieldName, "yep", "", true, "true or false"}, ) } - // FundraisingFieldName - cases = append(cases, - testCase{"Fundraising valid", fundraisingFieldName, FundraisingOptions[0], "", false, ""}, - testCase{"Fundraising invalid", fundraisingFieldName, "explicitly not an option", "", true, "valid options are"}, - ) // EmailAddressFieldName cases = append(cases, testCase{"EmailAddress valid email", emailAddressFieldName, "foo@bar.co", "", false, ""}, @@ -449,7 +436,6 @@ func TestSubmitOverrideRequestHandlerRateLimited(t *testing.T) { subscriberAgreementFieldName: "true", privacyPolicyFieldName: "true", mailingListFieldName: "false", - fundraisingFieldName: FundraisingOptions[0], emailAddressFieldName: "foo@bar.co", OrganizationFieldName: "Big Host Inc.", useCaseFieldName: strings.Repeat("x", 60), @@ -527,7 +513,6 @@ func TestSubmitOverrideRequestHandlerAutoApproved(t *testing.T) { subscriberAgreementFieldName: "true", privacyPolicyFieldName: "true", mailingListFieldName: "false", - fundraisingFieldName: FundraisingOptions[0], emailAddressFieldName: "foo@bar.co", OrganizationFieldName: "Big Host Inc.", useCaseFieldName: strings.Repeat("x", 60), diff --git a/sfe/sfe.go b/sfe/sfe.go index 3dee66520e4..fd2c8e618ca 100644 --- a/sfe/sfe.go +++ b/sfe/sfe.go @@ -23,7 +23,7 @@ import ( rapb "github.com/letsencrypt/boulder/ra/proto" rl "github.com/letsencrypt/boulder/ratelimits" sapb "github.com/letsencrypt/boulder/sa/proto" - salesforcepb "github.com/letsencrypt/boulder/salesforce/proto" + emailpb "github.com/letsencrypt/boulder/salesforce/email/proto" "github.com/letsencrypt/boulder/sfe/zendesk" "github.com/letsencrypt/boulder/unpause" ) @@ -57,7 +57,7 @@ var ( type SelfServiceFrontEndImpl struct { ra rapb.RegistrationAuthorityClient sa sapb.StorageAuthorityReadOnlyClient - ee salesforcepb.ExporterClient + ee emailpb.ExporterClient log blog.Logger clk clock.Clock @@ -87,7 +87,7 @@ func NewSelfServiceFrontEndImpl( requestTimeout time.Duration, rac rapb.RegistrationAuthorityClient, sac sapb.StorageAuthorityReadOnlyClient, - eec salesforcepb.ExporterClient, + eec emailpb.ExporterClient, unpauseHMACKey []byte, zendeskClient *zendesk.Client, limiter *rl.Limiter, diff --git a/test/config-next/email-exporter.json b/test/config-next/email-exporter.json index 552a57b6e85..fd6a93ca35a 100644 --- a/test/config-next/email-exporter.json +++ b/test/config-next/email-exporter.json @@ -11,12 +11,6 @@ "sfe.boulder" ] }, - "salesforce.Exporter": { - "clientNames": [ - "wfe.boulder", - "sfe.boulder" - ] - }, "grpc.health.v1.Health": { "clientNames": [ "health-checker.boulder" diff --git a/test/integration/email_exporter_test.go b/test/integration/email_exporter_test.go index 3bffaea7a10..64ebe191dd3 100644 --- a/test/integration/email_exporter_test.go +++ b/test/integration/email_exporter_test.go @@ -3,7 +3,6 @@ package integration import ( - "bytes" "crypto/ecdsa" "crypto/elliptic" "crypto/rand" @@ -167,161 +166,3 @@ func TestContactsSentForNewAccount(t *testing.T) { } } -// getCreatedCases queries the salesforce-test-srv for the list of created cases. -// Fails the test on error. -func getCreatedCases(t *testing.T, token string) []map[string]any { - t.Helper() - - req, err := http.NewRequest("GET", "http://localhost:9601/cases", nil) - test.AssertNotError(t, err, "Failed to create cases request") - req.Header.Set("Authorization", "Bearer "+token) - - resp, err := http.DefaultClient.Do(req) - test.AssertNotError(t, err, "Failed to query cases") - test.AssertEquals(t, resp.StatusCode, http.StatusOK) - defer resp.Body.Close() - - var got struct { - Cases []map[string]any `json:"cases"` - } - err = json.NewDecoder(resp.Body).Decode(&got) - test.AssertNotError(t, err, "Failed to decode cases") - return got.Cases -} - -// createCase sends a request to create a new case via salesforce-test-srv and -// returns the HTTP status code and response body. Fails the test on error. -func createCase(t *testing.T, token string, payload map[string]any) (int, []byte) { - t.Helper() - - b, err := json.Marshal(payload) - test.AssertNotError(t, err, "Failed to marshal case payload") - - req, err := http.NewRequest( - "POST", - "http://localhost:9601/services/data/v64.0/sobjects/Case", - bytes.NewReader(b), - ) - test.AssertNotError(t, err, "Failed to create case POST request") - req.Header.Set("Authorization", "Bearer "+token) - req.Header.Set("Content-Type", "application/json") - - resp, err := http.DefaultClient.Do(req) - test.AssertNotError(t, err, "Failed to POST case") - defer resp.Body.Close() - - var body bytes.Buffer - _, err = body.ReadFrom(resp.Body) - test.AssertNotError(t, err, "Failed to read case response body") - - return resp.StatusCode, body.Bytes() -} - -func TestCasesAPISuccess(t *testing.T) { - t.Parallel() - - token := getOAuthToken(t) - - status, _ := createCase(t, token, map[string]any{ - "Subject": "Integration Test Case", - "Description": "Created by integration test", - "Origin": "Web", - }) - test.AssertEquals(t, status, http.StatusCreated) - - // Verify it was recorded by the fake server. - cases := getCreatedCases(t, token) - found := false - for _, c := range cases { - if c["Subject"] == "Integration Test Case" && c["Origin"] == "Web" { - found = true - break - } - } - if !found { - t.Fatalf("Expected created case to be present; got cases=%s", cases) - } -} - -func TestCasesAPIMissingOrigin(t *testing.T) { - t.Parallel() - - token := getOAuthToken(t) - - // Missing Origin should be rejected by the fake server. - status, body := createCase(t, token, map[string]any{ - "Subject": "Missing Origin Case", - "Description": "Should fail", - }) - test.AssertEquals(t, status, http.StatusBadRequest) - test.AssertContains(t, string(body), "Missing required field: Origin") -} - -func TestCasesAPIUsingSFE(t *testing.T) { - t.Parallel() - - if os.Getenv("BOULDER_CONFIG_DIR") != "test/config-next" { - t.Skip("Test requires SFE to be configured to use email-exporter") - } - - token := getOAuthToken(t) - - body, err := json.Marshal(map[string]any{ - "rateLimit": "NewOrdersPerAccount", - "fields": map[string]string{ - "subscriberAgreement": "true", - "privacyPolicy": "true", - "mailingList": "false", - "fundraising": "Yes, email me more information.", - "emailAddress": "test@foo.bar", - "organization": "Big Host Inc.", - "useCase": strings.Repeat("x", 60), - "tier": "1000", - "accountURI": "https://acme-v02.api.letsencrypt.org/acme/acct/12345", - }, - }) - test.AssertNotError(t, err, "marshal override payload") - - req, err := http.NewRequest(http.MethodPost, "http://localhost:4003/sfe/v1/overrides/submit-override-request", bytes.NewReader(body)) - test.AssertNotError(t, err, "creating override request") - req.Header.Set("Content-Type", "application/json") - - resp, err := http.DefaultClient.Do(req) - test.AssertNotError(t, err, "POSTing override request to SFE") - defer resp.Body.Close() - - if resp.StatusCode != http.StatusCreated { - var buf bytes.Buffer - _, err = buf.ReadFrom(resp.Body) - test.AssertNotError(t, err, "reading SFE response body") - t.Errorf("unexpected SFE status=%d with body=%s", resp.StatusCode, buf.String()) - } - - timeout := 3 * time.Second - interval := 10 * time.Millisecond - - ticker := time.NewTicker(interval) - defer ticker.Stop() - - timer := time.NewTimer(timeout) - defer timer.Stop() - - for { - select { - case <-ticker.C: - cases := getCreatedCases(t, token) - for _, c := range cases { - if c["Subject"] == "NewOrdersPerAccount rate limit override request for Big Host Inc." && - c["Origin"] == "Web" && - c["ContactEmail"] == "test@foo.bar" && - c["Organization__c"] == "Big Host Inc." && - c["Rate_Limit_Name__c"] == "NewOrdersPerAccount" && - c["Rate_Limit_Tier__c"] == "1000" { - return - } - } - case <-timer.C: - t.Fatalf("expected Case never created within %s", timeout) - } - } -} diff --git a/test/salesforce-test-srv/main.go b/test/salesforce-test-srv/main.go index ac34953bf2a..ad007fdeaaf 100644 --- a/test/salesforce-test-srv/main.go +++ b/test/salesforce-test-srv/main.go @@ -48,17 +48,11 @@ type contacts struct { created []string } -type cases struct { - sync.Mutex - created []map[string]any -} - type testServer struct { expectedClientID string expectedClientSecret string token string contacts contacts - cases cases } func (ts *testServer) getTokenHandler(w http.ResponseWriter, r *http.Request) { @@ -160,54 +154,6 @@ func (ts *testServer) queryContactsHandler(w http.ResponseWriter, r *http.Reques } } -func (ts *testServer) createCaseHandler(w http.ResponseWriter, r *http.Request) { - ts.checkToken(w, r) - - var payload map[string]any - if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { - http.Error(w, "Invalid JSON", http.StatusBadRequest) - return - } - - _, ok := payload["Origin"] - if !ok { - http.Error(w, "Missing required field: Origin", http.StatusBadRequest) - return - } - - ts.cases.Lock() - ts.cases.created = append(ts.cases.created, payload) - ts.cases.Unlock() - - resp := map[string]any{ - "id": fmt.Sprintf("500xx00000%06dAAA", len(ts.cases.created)+1), - "success": true, - "errors": []string{}, - } - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusCreated) - err := json.NewEncoder(w).Encode(resp) - if err != nil { - log.Printf("Failed to encode case creation response: %s", err) - http.Error(w, "Failed to encode case creation response", http.StatusInternalServerError) - } -} - -func (ts *testServer) queryCasesHandler(w http.ResponseWriter, r *http.Request) { - ts.checkToken(w, r) - - ts.cases.Lock() - respCases := slices.Clone(ts.cases.created) - ts.cases.Unlock() - - w.Header().Set("Content-Type", "application/json") - err := json.NewEncoder(w).Encode(map[string]any{"cases": respCases}) - if err != nil { - log.Printf("Failed to encode cases query response: %v", err) - http.Error(w, "Failed to encode cases query response", http.StatusInternalServerError) - } -} - func main() { // TODO(#8410): Remove the oauthAddr flag. oauthAddr := flag.String("oauth-addr", "", "Salesforce REST API and OAuth server listen address override (deprecated: use --salesforce-addr instead)") @@ -255,14 +201,11 @@ func main() { expectedClientSecret: c.ExpectedClientSecret, token: fmt.Sprintf("%x", tokenBytes), contacts: contacts{created: make([]string, 0, contactsCap)}, - cases: cases{created: make([]map[string]any, 0)}, } // Salesforce REST API and OAuth Server oauthMux := http.NewServeMux() oauthMux.HandleFunc("/services/oauth2/token", ts.getTokenHandler) - oauthMux.HandleFunc("/services/data/v64.0/sobjects/Case", ts.createCaseHandler) - oauthMux.HandleFunc("/cases", ts.queryCasesHandler) salesforceServer := &http.Server{ Addr: c.SalesforceAddr, Handler: oauthMux, diff --git a/wfe2/wfe.go b/wfe2/wfe.go index aaaf494170f..5f37724ef9d 100644 --- a/wfe2/wfe.go +++ b/wfe2/wfe.go @@ -44,7 +44,7 @@ import ( "github.com/letsencrypt/boulder/ratelimits" "github.com/letsencrypt/boulder/revocation" sapb "github.com/letsencrypt/boulder/sa/proto" - salesforcepb "github.com/letsencrypt/boulder/salesforce/proto" + emailpb "github.com/letsencrypt/boulder/salesforce/email/proto" "github.com/letsencrypt/boulder/unpause" "github.com/letsencrypt/boulder/web" ) @@ -92,7 +92,7 @@ var errIncompleteGRPCResponse = errors.New("incomplete gRPC response message") type WebFrontEndImpl struct { ra rapb.RegistrationAuthorityClient sa sapb.StorageAuthorityReadOnlyClient - ee salesforcepb.ExporterClient + ee emailpb.ExporterClient // gnc is a nonce-service client used exclusively for the issuance of // nonces. It's configured to route requests to backends colocated with the // WFE. @@ -198,7 +198,7 @@ func NewWebFrontEndImpl( maxContactsPerReg int, rac rapb.RegistrationAuthorityClient, sac sapb.StorageAuthorityReadOnlyClient, - eec salesforcepb.ExporterClient, + eec emailpb.ExporterClient, gnc nonce.Getter, rnc nonce.Redeemer, rncKey []byte, @@ -930,7 +930,7 @@ func (wfe *WebFrontEndImpl) NewAccount( newRegistrationSuccessful = true if wfe.ee != nil && len(emails) > 0 { - _, err := wfe.ee.SendContacts(ctx, &salesforcepb.SendContactsRequest{ + _, err := wfe.ee.SendContacts(ctx, &emailpb.SendContactsRequest{ // Note: We are explicitly using the contacts provided by the // subscriber here. The RA will eventually stop accepting contacts. Emails: emails,