Skip to content

Commit f97e57a

Browse files
sfe: Add configurable automatic approvals for first-tier limits
1 parent 3c3c365 commit f97e57a

File tree

10 files changed

+270
-161
lines changed

10 files changed

+270
-161
lines changed

cmd/sfe/main.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,14 @@ type Config struct {
9494
// 20 minutes.
9595
Interval config.Duration `validate:"omitempty,required_with=Mode,min=1200s"`
9696
} `validate:"omitempty,dive"`
97-
Features features.Config
97+
98+
// AutoApproveOverrides enables automatic approval of override requests
99+
// for the following limits and tiers:
100+
// - NewOrdersPerAccount: 1000
101+
// - CertificatesPerDomain: 300
102+
// - CertificatesPerDomainPerAccount: 300
103+
AutoApproveOverrides bool `validate:"-"`
104+
Features features.Config
98105
}
99106

100107
Syslog cmd.SyslogConfig
@@ -232,6 +239,7 @@ func main() {
232239
zendeskClient,
233240
limiter,
234241
txnBuilder,
242+
c.SFE.AutoApproveOverrides,
235243
)
236244
cmd.FailOnError(err, "Unable to create SFE")
237245

sfe/overrides.go

Lines changed: 112 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package sfe
22

33
import (
4+
"context"
45
"encoding/json"
56
"errors"
67
"fmt"
@@ -269,56 +270,39 @@ func makeInitialComment(organization, useCase, tier string) string {
269270
)
270271
}
271272

272-
// createNewOrdersPerAccountOverrideTicket creates a new Zendesk ticket for a
273-
// NewOrdersPerAccount override request. All fields are required.
274-
func createNewOrdersPerAccountOverrideTicket(client *zendesk.Client, requesterEmail, useCase, organization, tier, accountID string) (int64, error) {
275-
return client.CreateTicket(
276-
requesterEmail,
277-
makeSubject(rl.NewOrdersPerAccount, organization),
278-
makeInitialComment(organization, useCase, tier),
279-
map[string]string{
280-
RateLimitFieldName: rl.NewOrdersPerAccount.String(),
281-
ReviewStatusFieldName: reviewStatusDefault,
282-
OrganizationFieldName: organization,
283-
TierFieldName: tier,
284-
AccountURIFieldName: accountID,
285-
},
286-
)
287-
}
273+
// createOverrideRequestZendeskTicket creates a new Zendesk ticket for manual
274+
// review of a rate limit override request. It returns the ID of the created
275+
// ticket or an error.
276+
func createOverrideRequestZendeskTicket(client *zendesk.Client, rateLimit, requesterEmail, useCase, organization, tier, accountURI, registeredDomain, ipAddress string) (int64, error) {
277+
// Some rateLimitField values include suffixes to indicate whether an
278+
// accountURI, registeredDomain, or ipAddress is expected.
279+
limitStr := strings.TrimSuffix(strings.TrimSuffix(rateLimit, perDNSNameSuffix), perIPSuffix)
280+
limit, ok := rl.StringToName[limitStr]
281+
if !ok {
282+
// This should never happen, it indicates a bug in our validation.
283+
return 0, errors.New("invalid rate limit prevented ticket creation")
284+
}
285+
286+
if registeredDomain == "" && ipAddress == "" && accountURI == "" {
287+
// This should never happen, it indicates a bug in our validation.
288+
return 0, errors.New("one of accountURI, registeredDomain, or ipAddress must be provided")
289+
}
288290

289-
// createCertificatesPerDomainOverrideTicket creates a new Zendesk ticket for a
290-
// CertificatesPerDomain override request. Only registeredDomain or ipAddress
291-
// should be provided, not both. All other fields are required.
292-
func createCertificatesPerDomainOverrideTicket(client *zendesk.Client, requesterEmail, useCase, organization, tier, registeredDomain, ipAddress string) (int64, error) {
293291
return client.CreateTicket(
294292
requesterEmail,
295-
makeSubject(rl.CertificatesPerDomain, organization),
293+
// The stripped form of the rateLimitField value must be used here.
294+
makeSubject(limit, organization),
296295
makeInitialComment(organization, useCase, tier),
297296
map[string]string{
298-
RateLimitFieldName: rl.CertificatesPerDomain.String(),
297+
// The original rateLimitField value must be used here, the
298+
// overridesimporter depends on the suffixes for validation.
299+
RateLimitFieldName: rateLimit,
300+
TierFieldName: tier,
299301
ReviewStatusFieldName: reviewStatusDefault,
300302
OrganizationFieldName: organization,
301-
TierFieldName: tier,
302303
RegisteredDomainFieldName: registeredDomain,
303304
IPAddressFieldName: ipAddress,
304-
},
305-
)
306-
}
307-
308-
// createCertificatesPerDomainPerAccountOverrideTicket creates a new Zendesk
309-
// ticket for a CertificatesPerDomainPerAccount override request. All fields are
310-
// required.
311-
func createCertificatesPerDomainPerAccountOverrideTicket(client *zendesk.Client, requesterEmail, useCase, organization, tier, accountID string) (int64, error) {
312-
return client.CreateTicket(
313-
requesterEmail,
314-
makeSubject(rl.CertificatesPerDomainPerAccount, organization),
315-
makeInitialComment(organization, useCase, tier),
316-
map[string]string{
317-
RateLimitFieldName: rl.CertificatesPerDomainPerAccount.String(),
318-
ReviewStatusFieldName: reviewStatusDefault,
319-
OrganizationFieldName: organization,
320-
TierFieldName: tier,
321-
AccountURIFieldName: accountID,
305+
AccountURIFieldName: accountURI,
322306
},
323307
)
324308
}
@@ -489,12 +473,13 @@ func (sfe *SelfServiceFrontEndImpl) makeOverrideRequestFormHandler(formHTML temp
489473
func (sfe *SelfServiceFrontEndImpl) overrideRequestHandler(w http.ResponseWriter, formHTML template.HTML, rateLimit, displayRateLimit string) {
490474
setOverrideRequestFormHeaders(w)
491475
sfe.renderTemplate(w, "overrideForm.html", map[string]any{
492-
"FormHTML": formHTML,
493-
"RateLimit": rateLimit,
494-
"DisplayRateLimit": displayRateLimit,
495-
"ValidateFieldPath": overridesValidateField,
496-
"SubmitRequestPath": overridesSubmitRequest,
497-
"SubmitSuccessPath": overridesSubmitSuccess,
476+
"FormHTML": formHTML,
477+
"RateLimit": rateLimit,
478+
"DisplayRateLimit": displayRateLimit,
479+
"ValidateFieldPath": overridesValidateField,
480+
"SubmitRequestPath": overridesSubmitRequest,
481+
"CreatedSuccessPath": overridesCreatedSuccess,
482+
"AcceptedSuccessPath": overridesAcceptedSuccess,
498483
})
499484
}
500485

@@ -542,10 +527,16 @@ func (sfe *SelfServiceFrontEndImpl) validateOverrideFieldHandler(w http.Response
542527
}
543528
}
544529

545-
// overrideSuccessHandler renders the success page after a successful override
546-
// request submission.
547-
func (sfe *SelfServiceFrontEndImpl) overrideSuccessHandler(w http.ResponseWriter, r *http.Request) {
548-
sfe.renderTemplate(w, "overrideSuccess.html", nil)
530+
// overrideCreatedSuccessHandler renders the success page after a successful
531+
// override request submission which was automatically approved.
532+
func (sfe *SelfServiceFrontEndImpl) overrideCreatedSuccessHandler(w http.ResponseWriter, r *http.Request) {
533+
sfe.renderTemplate(w, "overrideCreatedSuccess.html", nil)
534+
}
535+
536+
// overrideAcceptedSuccessHandler renders the success page after a successful
537+
// override request submission which created a Zendesk ticket for manual review.
538+
func (sfe *SelfServiceFrontEndImpl) overrideAcceptedSuccessHandler(w http.ResponseWriter, r *http.Request) {
539+
sfe.renderTemplate(w, "overrideAcceptedSuccess.html", nil)
549540
}
550541

551542
type overrideRequest struct {
@@ -555,9 +546,11 @@ type overrideRequest struct {
555546

556547
// submitOverrideRequestHandler handles the submission of override requests. It
557548
// expects a POST request with a JSON payload (overrideRequest). It validates
558-
// each of the form fields and creates a Zendesk ticket based on the specified
559-
// rate limit. It returns a 200 OK response on success, or an error response if
560-
// the request is invalid or if ticket creation fails.
549+
// each of the form fields and either:
550+
//
551+
// a. auto-approves the override request and returns 201 Created, or
552+
// b. creates a Zendesk ticket for manual review, and returns 202 Accepted, or
553+
// c. encounters an error and returns an appropriate 4xx or 5xx status code.
561554
//
562555
// The JavaScript frontend is configured to validate the form fields twice: once
563556
// when the requester inputs data, and once more just before submitting the
@@ -566,7 +559,6 @@ type overrideRequest struct {
566559
// submitting (malformed) requests directly to this endpoint.
567560
func (sfe *SelfServiceFrontEndImpl) submitOverrideRequestHandler(w http.ResponseWriter, r *http.Request) {
568561
var refundLimits func()
569-
var submissionSuccess bool
570562
if sfe.limiter != nil && sfe.txnBuilder != nil {
571563
requesterIP, err := web.ExtractRequesterIP(r)
572564
if err != nil {
@@ -608,8 +600,10 @@ func (sfe *SelfServiceFrontEndImpl) submitOverrideRequestHandler(w http.Response
608600
}
609601
}
610602
}
603+
var created bool
604+
var accepted bool
611605
defer func() {
612-
if !submissionSuccess && refundLimits != nil {
606+
if !created && !accepted && refundLimits != nil {
613607
refundLimits()
614608
}
615609
}()
@@ -635,7 +629,7 @@ func (sfe *SelfServiceFrontEndImpl) submitOverrideRequestHandler(w http.Response
635629
return val, nil
636630
}
637631

638-
var baseFields = make(map[string]string)
632+
var validFields = make(map[string]string)
639633
for _, name := range []string{
640634
// Note: not all of these fields will be included in the Zendesk ticket,
641635
// but they are all required for the submission to be considered valid.
@@ -653,7 +647,24 @@ func (sfe *SelfServiceFrontEndImpl) submitOverrideRequestHandler(w http.Response
653647
http.Error(w, err.Error(), http.StatusBadRequest)
654648
return
655649
}
656-
baseFields[name] = val
650+
validFields[name] = val
651+
}
652+
653+
autoApproveOverride := func(ctx context.Context, rateLimitFieldValue string, fields map[string]string) bool {
654+
if !sfe.autoApproveOverrides {
655+
return false
656+
}
657+
req, _, err := makeAddOverrideRequest(rateLimitFieldValue, fields)
658+
if err != nil {
659+
sfe.log.Errf("failed to create automatically approved override request: %s", err)
660+
return false
661+
}
662+
resp, err := sfe.ra.AddRateLimitOverride(ctx, req)
663+
if err != nil {
664+
sfe.log.Errf("failed to create automatically approved override request: %s", err)
665+
return false
666+
}
667+
return resp.Enabled
657668
}
658669

659670
switch req.RateLimit {
@@ -663,22 +674,10 @@ func (sfe *SelfServiceFrontEndImpl) submitOverrideRequestHandler(w http.Response
663674
http.Error(w, err.Error(), http.StatusBadRequest)
664675
return
665676
}
677+
validFields[AccountURIFieldName] = accountURI
666678

667-
// TODO(#8360): Skip ticket creation and insert an override for
668-
// overrides matching the first N tiers of this limit.
669-
670-
_, err = createNewOrdersPerAccountOverrideTicket(
671-
sfe.zendeskClient,
672-
baseFields[emailAddressFieldName],
673-
baseFields[useCaseFieldName],
674-
baseFields[OrganizationFieldName],
675-
baseFields[TierFieldName],
676-
accountURI,
677-
)
678-
if err != nil {
679-
sfe.log.Errf("failed to create override request ticket: %s", err)
680-
http.Error(w, "failed to create override request ticket", http.StatusInternalServerError)
681-
return
679+
if validFields[TierFieldName] == newOrdersPerAccountTierOptions[0] {
680+
created = autoApproveOverride(r.Context(), req.RateLimit, validFields)
682681
}
683682

684683
case rl.CertificatesPerDomainPerAccount.String():
@@ -687,22 +686,10 @@ func (sfe *SelfServiceFrontEndImpl) submitOverrideRequestHandler(w http.Response
687686
http.Error(w, err.Error(), http.StatusBadRequest)
688687
return
689688
}
689+
validFields[AccountURIFieldName] = accountURI
690690

691-
// TODO(#8360): Skip ticket creation and insert an override for
692-
// overrides matching the first N tiers of this limit.
693-
694-
_, err = createCertificatesPerDomainPerAccountOverrideTicket(
695-
sfe.zendeskClient,
696-
baseFields[emailAddressFieldName],
697-
baseFields[useCaseFieldName],
698-
baseFields[OrganizationFieldName],
699-
baseFields[TierFieldName],
700-
accountURI,
701-
)
702-
if err != nil {
703-
sfe.log.Errf("failed to create override request ticket: %s", err)
704-
http.Error(w, "failed to create override request ticket", http.StatusInternalServerError)
705-
return
691+
if validFields[TierFieldName] == certificatesPerDomainPerAccountTierOptions[0] {
692+
created = autoApproveOverride(r.Context(), req.RateLimit, validFields)
706693
}
707694

708695
case rl.CertificatesPerDomain.String() + perDNSNameSuffix:
@@ -711,23 +698,10 @@ func (sfe *SelfServiceFrontEndImpl) submitOverrideRequestHandler(w http.Response
711698
http.Error(w, err.Error(), http.StatusBadRequest)
712699
return
713700
}
701+
validFields[RegisteredDomainFieldName] = registeredDomain
714702

715-
// TODO(#8360): Skip ticket creation and insert an override for
716-
// overrides matching the first N tiers of this limit.
717-
718-
_, err = createCertificatesPerDomainOverrideTicket(
719-
sfe.zendeskClient,
720-
baseFields[emailAddressFieldName],
721-
baseFields[useCaseFieldName],
722-
baseFields[OrganizationFieldName],
723-
baseFields[TierFieldName],
724-
registeredDomain,
725-
"",
726-
)
727-
if err != nil {
728-
sfe.log.Errf("failed to create override request ticket: %s", err)
729-
http.Error(w, "failed to create override request ticket", http.StatusInternalServerError)
730-
return
703+
if validFields[TierFieldName] == certificatesPerDomainTierOptions[0] {
704+
created = autoApproveOverride(r.Context(), req.RateLimit, validFields)
731705
}
732706

733707
case rl.CertificatesPerDomain.String() + perIPSuffix:
@@ -736,32 +710,19 @@ func (sfe *SelfServiceFrontEndImpl) submitOverrideRequestHandler(w http.Response
736710
http.Error(w, err.Error(), http.StatusBadRequest)
737711
return
738712
}
713+
validFields[IPAddressFieldName] = ipAddress
739714

740-
// TODO(#8360): Skip ticket creation and insert an override for
741-
// overrides matching the first N tiers of this limit.
742-
743-
_, err = createCertificatesPerDomainOverrideTicket(
744-
sfe.zendeskClient,
745-
baseFields[emailAddressFieldName],
746-
baseFields[useCaseFieldName],
747-
baseFields[OrganizationFieldName],
748-
baseFields[TierFieldName],
749-
"",
750-
ipAddress,
751-
)
752-
if err != nil {
753-
sfe.log.Errf("failed to create override request ticket: %s", err)
754-
http.Error(w, "failed to create override request ticket", http.StatusInternalServerError)
755-
return
715+
if validFields[TierFieldName] == certificatesPerDomainTierOptions[0] {
716+
created = autoApproveOverride(r.Context(), req.RateLimit, validFields)
756717
}
757718

758719
default:
759720
http.Error(w, "unknown rate limit", http.StatusBadRequest)
760721
return
761722
}
762723

763-
if sfe.ee != nil && baseFields[fundraisingFieldName] == fundraisingYesOption {
764-
_, err := sfe.ee.SendContacts(r.Context(), &emailpb.SendContactsRequest{Emails: []string{baseFields[emailAddressFieldName]}})
724+
if sfe.ee != nil && validFields[fundraisingFieldName] == fundraisingYesOption {
725+
_, err := sfe.ee.SendContacts(r.Context(), &emailpb.SendContactsRequest{Emails: []string{validFields[emailAddressFieldName]}})
765726
if err != nil {
766727
sfe.log.Errf("failed to send contact to email service: %s", err)
767728
}
@@ -770,6 +731,33 @@ func (sfe *SelfServiceFrontEndImpl) submitOverrideRequestHandler(w http.Response
770731
// TODO(#8362): If FundraisingFieldName value is true, use the Salesforce
771732
// API to create a new Lead record with the provided information.
772733

773-
submissionSuccess = true
774-
w.WriteHeader(http.StatusOK)
734+
if created {
735+
sfe.log.Infof("automatically approved override request for %s", validFields[OrganizationFieldName])
736+
w.WriteHeader(http.StatusCreated)
737+
return
738+
}
739+
740+
ticketID, err := createOverrideRequestZendeskTicket(
741+
sfe.zendeskClient,
742+
req.RateLimit,
743+
validFields[emailAddressFieldName],
744+
validFields[useCaseFieldName],
745+
validFields[OrganizationFieldName],
746+
validFields[TierFieldName],
747+
748+
// Only one of these will be non-empty, depending on the
749+
// rateLimitField value.
750+
validFields[AccountURIFieldName],
751+
validFields[RegisteredDomainFieldName],
752+
validFields[IPAddressFieldName],
753+
)
754+
if err != nil {
755+
sfe.log.Errf("failed to create override request Zendesk ticket: %s", err)
756+
http.Error(w, "failed to create support ticket", http.StatusInternalServerError)
757+
return
758+
}
759+
760+
accepted = true
761+
sfe.log.Infof("created override request Zendesk ticket %d", ticketID)
762+
w.WriteHeader(http.StatusAccepted)
775763
}

0 commit comments

Comments
 (0)