github.com/letsencrypt/boulder@v0.20251208.0/sfe/overrides.go (about)

     1  package sfe
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"html/template"
     9  	"net/http"
    10  	"net/url"
    11  	"slices"
    12  	"strconv"
    13  	"strings"
    14  
    15  	emailpb "github.com/letsencrypt/boulder/email/proto"
    16  	berrors "github.com/letsencrypt/boulder/errors"
    17  	"github.com/letsencrypt/boulder/iana"
    18  	"github.com/letsencrypt/boulder/policy"
    19  	rl "github.com/letsencrypt/boulder/ratelimits"
    20  	"github.com/letsencrypt/boulder/sfe/forms"
    21  	"github.com/letsencrypt/boulder/sfe/zendesk"
    22  	"github.com/letsencrypt/boulder/web"
    23  )
    24  
    25  const (
    26  	apiVersion         = "v1"
    27  	overridesAPIPrefix = "/sfe/" + apiVersion
    28  
    29  	// Meta fields (not user-entered)
    30  	ReviewStatusFieldName = "reviewStatus"
    31  	RateLimitFieldName    = "rateLimit"
    32  
    33  	// Shared user-entered fields (UI + API/Zendesk)
    34  	OrganizationFieldName     = "organization"
    35  	TierFieldName             = "tier"
    36  	AccountURIFieldName       = "accountURI"
    37  	RegisteredDomainFieldName = "registeredDomain"
    38  	IPAddressFieldName        = "ipAddress"
    39  
    40  	// UI-only fields
    41  	subscriberAgreementFieldName = "subscriberAgreement"
    42  	privacyPolicyFieldName       = "privacyPolicy"
    43  	emailAddressFieldName        = "emailAddress"
    44  	useCaseFieldName             = "useCase"
    45  	fundraisingFieldName         = "fundraising"
    46  	mailingListFieldName         = "mailingList"
    47  
    48  	// reviewStatusDefault is the initial status of a ticket when created.
    49  	reviewStatusDefault = "review-status-pending"
    50  
    51  	// reviewStatusApproved is the status of a ticket when it has been approved.
    52  	reviewStatusApproved = "review-status-approved"
    53  
    54  	// validateOverrideFieldBodyLimit is the maximum size of request body
    55  	// accepted by validateOverrideFieldHandler. It should be large enough to
    56  	// accommodate the JSON encoded validationRequest struct, but small enough
    57  	// to avoid abuse.
    58  	//
    59  	// It is currently set to 5 KiB, which is more than enough for even the
    60  	// longest "Use Case" field values.
    61  	validateOverrideFieldBodyLimit = 5 << 10
    62  
    63  	// validateOverrideFieldBodyLimit is the maximum size of request body
    64  	// accepted by validateOverrideFieldHandler. It should be large enough to
    65  	// accommodate the JSON encoded overrideRequest struct, but small enough to
    66  	// avoid abuse.
    67  	//
    68  	// It is currently set to 10 KiB, which is more than enough for the expected
    69  	// request size.
    70  	submitOverrideRequestBodyLimit = 10 << 10
    71  
    72  	// These are suffixes added to the rate limit names to differentiate between
    73  	// two different forms that request overrides for CertificatesPerDomain.
    74  	perDNSNameSuffix = "_dnsName"
    75  	perIPSuffix      = "_ipAddr"
    76  )
    77  
    78  var (
    79  	// NOTE: If you modify one of the tier slices below, ensure that you have
    80  	// already updated the corresponding dropdown in the Zendesk dashboard.
    81  	// Failing to do so will result in the override request form not being able
    82  	// to process the request.
    83  
    84  	// newOrdersPerAccountTierOptions is the list of valid tiers for the
    85  	// NewOrdersPerAccount rate limit override requests.
    86  	newOrdersPerAccountTierOptions = []string{"1000", "5000", "10000", "25000", "50000", "75000", "100000", "175000", "250000", "500000", "750000", "1000000"}
    87  
    88  	// certificatesPerDomainTierOptions is the list of valid tiers for the
    89  	// CertificatesPerDomain rate limit.
    90  	certificatesPerDomainTierOptions = []string{"300", "1000", "5000", "10000", "25000", "50000", "75000", "100000", "175000", "250000", "500000", "1000000"}
    91  
    92  	// certificatesPerDomainPerAccountTierOptions is the list of valid tiers for
    93  	// the CertificatesPerDomainPerAccount rate limit override requests.
    94  	certificatesPerDomainPerAccountTierOptions = []string{"300", "1000", "5000", "10000", "25000", "50000", "75000", "100000", "175000", "250000", "500000", "1000000", "1750000", "2500000"}
    95  
    96  	fundraisingYesOption = "Yes, email me more information."
    97  
    98  	// FundraisingOptions is the list of options for the fundraising field.
    99  	FundraisingOptions = []string{
   100  		fundraisingYesOption,
   101  		"No, not at this time.",
   102  	}
   103  
   104  	// tierOptionsByRateLimit maps rate limit names to their valid tiers.
   105  	tierOptionsByRateLimit = map[string][]string{
   106  		rl.NewOrdersPerAccount.String():                      newOrdersPerAccountTierOptions,
   107  		rl.CertificatesPerDomain.String() + perDNSNameSuffix: certificatesPerDomainTierOptions,
   108  		rl.CertificatesPerDomain.String() + perIPSuffix:      certificatesPerDomainTierOptions,
   109  		rl.CertificatesPerDomainPerAccount.String():          certificatesPerDomainPerAccountTierOptions,
   110  	}
   111  
   112  	fundraisingField = forms.NewDropdownField(
   113  		"Did you know that Let's Encrypt is a non-profit project?",
   114  		fundraisingFieldName,
   115  		`Funding for Let's Encrypt comes from contributions from our community 
   116  of users and advocates. While financially supporting Let's Encrypt is completely 
   117  optional and not required to use the service, we depend on the generosity of users 
   118  like you.
   119  
   120  Would your organization consider financially supporting Let's Encrypt as a Sponsor?`,
   121  		FundraisingOptions,
   122  		true,
   123  	)
   124  
   125  	baseFields = []forms.Field{
   126  		forms.NewCheckboxField(
   127  			"Subscriber Agreement",
   128  			subscriberAgreementFieldName,
   129  			`I acknowledge that I have read and agree to the latest version of the 
   130  <a href="https://letsencrypt.org/repository" target="_blank">Let's Encrypt 
   131  Subscriber Agreement</a> and understand that my use of Let's Encrypt services is 
   132  subject to its terms.`,
   133  			true,
   134  		),
   135  		forms.NewCheckboxField(
   136  			"Privacy Policy",
   137  			privacyPolicyFieldName,
   138  			`By submitting this form, I acknowledge that the information provided
   139  will be processed in accordance with <a href="https://letsencrypt.org/privacy" 
   140  target="_blank">Let's Encrypt's Privacy Policy</a>. I understand that ISRG collects 
   141  and will process this information to evaluate my rate limit override request and to 
   142  provide certificate issuance and management services. In addition, depending on my 
   143  responses to questions below, ISRG may use this information to send me email updates 
   144  and sponsorship information.`,
   145  			true,
   146  		),
   147  		forms.NewCheckboxField(
   148  			"Mailing List",
   149  			mailingListFieldName,
   150  			"I’d like to receive email updates about Let's Encrypt and other ISRG projects.",
   151  			false,
   152  		),
   153  		forms.NewTextareaField(
   154  			"Use Case",
   155  			useCaseFieldName,
   156  			`Please describe the use case for this override request. This helps us
   157  understand the need for the override and how it will be used.`,
   158  			4,
   159  			true,
   160  		),
   161  		forms.NewInputField(
   162  			"Email Address",
   163  			emailAddressFieldName,
   164  			`An email address where we can reach you regarding this request.`,
   165  			true,
   166  		),
   167  		forms.NewInputField(
   168  			"Organization or Project",
   169  			OrganizationFieldName,
   170  			`This helps us understand who is requesting the override and find the right
   171  contact person if needed.`,
   172  			true,
   173  		),
   174  	}
   175  )
   176  
   177  // overridesForm creates a new form with the base fields and the provided custom
   178  // fields. The custom fields will appear after the baseFields and before the
   179  // fundraising field.
   180  func overridesForm(customFields ...forms.Field) *forms.Form {
   181  	return forms.NewForm(append(append(baseFields, customFields...), fundraisingField)...)
   182  }
   183  
   184  var (
   185  	newOrdersPerAccountForm = overridesForm(
   186  		forms.NewDropdownField(
   187  			"Maximum Orders Per Week",
   188  			TierFieldName,
   189  			`The number of orders per week needed for this account. Please select the
   190  number that best matches your needs.`,
   191  			newOrdersPerAccountTierOptions,
   192  			true,
   193  		),
   194  		forms.NewInputField(
   195  			"Account URI",
   196  			AccountURIFieldName,
   197  			`The ACME account URI you're requesting the override for. For example:
   198  https://acme-v02.api.letsencrypt.org/acme/acct/12345. Read more about Account
   199  IDs <a href="https://letsencrypt.org/docs/account-id">here</a>.`,
   200  			true,
   201  		),
   202  	).RenderForm()
   203  
   204  	certificatesPerDomainForm = overridesForm(
   205  		forms.NewDropdownField(
   206  			"Maximum Certificates Per Week",
   207  			TierFieldName,
   208  			`The number of certificates per week needed for this domain and all
   209  subdomains. Please select the number that best matches your needs.`,
   210  			certificatesPerDomainTierOptions,
   211  			true,
   212  		),
   213  		forms.NewInputField(
   214  			"Registered Domain Name",
   215  			RegisteredDomainFieldName,
   216  			`The registered domain name you're requesting the override for. This should
   217  be the base domain, for instance, example.com, not www.example.com or
   218  blog.example.com. For Internationalized Domain Names such as bücher.com, use the
   219  <a href="https://www.punycoder.com/">ASCII-compatible Punycode</a> form:
   220  xn--bcher-kva.com.`,
   221  			true,
   222  		),
   223  	).RenderForm()
   224  
   225  	certificatesPerDomainPerAccountForm = overridesForm(
   226  		forms.NewDropdownField(
   227  			"Maximum Certificates Per Week",
   228  			TierFieldName,
   229  			`The number of certificates per week per registered domain name or IP
   230  address included in certificates requested by this account. Please select the
   231  number that best matches your needs.`,
   232  			certificatesPerDomainPerAccountTierOptions,
   233  			true,
   234  		),
   235  		forms.NewInputField(
   236  			"Account URI",
   237  			AccountURIFieldName,
   238  			`The account URI you're requesting the override for, for example:
   239  https://acme-v02.api.letsencrypt.org/acme/acct/12345.`,
   240  			true,
   241  		),
   242  	).RenderForm()
   243  
   244  	certificatesPerIPForm = overridesForm(
   245  		forms.NewDropdownField(
   246  			"Maximum Certificates Per Week",
   247  			TierFieldName,
   248  			`The number of certificates per week needed for this IP address. Please
   249  select the number that best matches your needs.`,
   250  			certificatesPerDomainTierOptions,
   251  			true,
   252  		),
   253  		forms.NewInputField(
   254  			"IP Address",
   255  			IPAddressFieldName,
   256  			`The IPv4 or IPv6 address you're requesting the override for. This should
   257  be the public IP address included in the certificate itself.`,
   258  			true,
   259  		),
   260  	).RenderForm()
   261  )
   262  
   263  func makeSubject(rateLimit rl.Name, organization string) string {
   264  	return fmt.Sprintf("%s rate limit override request for %s", rateLimit.String(), organization)
   265  }
   266  
   267  func makeInitialComment(organization, useCase, tier string) string {
   268  	return fmt.Sprintf(
   269  		"Use case: %s\n\nRequested Override Tier: %s\n\nOrganization: %s",
   270  		useCase, tier, organization,
   271  	)
   272  }
   273  
   274  // createOverrideRequestZendeskTicket creates a new Zendesk ticket for manual
   275  // review of a rate limit override request. It returns the ID of the created
   276  // ticket or an error.
   277  func createOverrideRequestZendeskTicket(client *zendesk.Client, rateLimit, requesterEmail, useCase, organization, tier, accountURI, registeredDomain, ipAddress string) (int64, error) {
   278  	// Some rateLimitField values include suffixes to indicate whether an
   279  	// accountURI, registeredDomain, or ipAddress is expected.
   280  	limitStr := strings.TrimSuffix(strings.TrimSuffix(rateLimit, perDNSNameSuffix), perIPSuffix)
   281  	limit, ok := rl.StringToName[limitStr]
   282  	if !ok {
   283  		// This should never happen, it indicates a bug in our validation.
   284  		return 0, errors.New("invalid rate limit prevented ticket creation")
   285  	}
   286  
   287  	if registeredDomain == "" && ipAddress == "" && accountURI == "" {
   288  		// This should never happen, it indicates a bug in our validation.
   289  		return 0, errors.New("one of accountURI, registeredDomain, or ipAddress must be provided")
   290  	}
   291  
   292  	return client.CreateTicket(
   293  		requesterEmail,
   294  		// The stripped form of the rateLimitField value must be used here.
   295  		makeSubject(limit, organization),
   296  		makeInitialComment(organization, useCase, tier),
   297  		map[string]string{
   298  			// The original rateLimitField value must be used here, the
   299  			// overridesimporter depends on the suffixes for validation.
   300  			RateLimitFieldName:        rateLimit,
   301  			TierFieldName:             tier,
   302  			ReviewStatusFieldName:     reviewStatusDefault,
   303  			OrganizationFieldName:     organization,
   304  			RegisteredDomainFieldName: registeredDomain,
   305  			IPAddressFieldName:        ipAddress,
   306  			AccountURIFieldName:       accountURI,
   307  		},
   308  	)
   309  }
   310  
   311  // validateOverrideRequestField validates the provided field and value against
   312  // the specified rate limit name. It returns nil if the field is valid, or an
   313  // error if it is not.
   314  func validateOverrideRequestField(fieldName, fieldValue, rateLimit string) error {
   315  	if fieldName == "" {
   316  		return fmt.Errorf("field name cannot be empty")
   317  	}
   318  	if fieldValue == "" {
   319  		return fmt.Errorf("%q cannot be empty", fieldName)
   320  	}
   321  	if rateLimit == "" && fieldName == TierFieldName {
   322  		return fmt.Errorf("a rate limit name must be specified")
   323  	}
   324  
   325  	switch fieldName {
   326  	case mailingListFieldName:
   327  		// This field is optional, so we only validate it is a boolean.
   328  		if fieldValue != "true" && fieldValue != "false" {
   329  			return fmt.Errorf("mailing list field must be true or false")
   330  		}
   331  		return nil
   332  
   333  	case subscriberAgreementFieldName, privacyPolicyFieldName:
   334  		agreed, err := strconv.ParseBool(fieldValue)
   335  		if err != nil {
   336  			return fmt.Errorf("subscriber agreement and privacy policy must be true or false")
   337  		}
   338  		if !agreed {
   339  			return fmt.Errorf("agreement with our subscriber agreement and privacy policy is required")
   340  		}
   341  		return nil
   342  
   343  	case fundraisingFieldName:
   344  		if !slices.Contains(FundraisingOptions, fieldValue) {
   345  			return fmt.Errorf("invalid fundraising option, valid options are: %s", strings.Join(FundraisingOptions, ", "))
   346  		}
   347  		return nil
   348  
   349  	case emailAddressFieldName:
   350  		err := policy.ValidEmail(fieldValue)
   351  		if err == nil {
   352  			return nil
   353  		}
   354  		return fmt.Errorf("email address is invalid")
   355  
   356  	case OrganizationFieldName:
   357  		if len(fieldValue) >= 5 {
   358  			return nil
   359  		}
   360  		return fmt.Errorf("organization or project must be at least five (5) characters long")
   361  
   362  	case useCaseFieldName:
   363  		if len(fieldValue) >= 60 {
   364  			return nil
   365  		}
   366  		return fmt.Errorf("use case must be at least 60 characters long")
   367  
   368  	case IPAddressFieldName:
   369  		err := policy.ValidIP(fieldValue)
   370  		if err == nil {
   371  			return nil
   372  		}
   373  		return fmt.Errorf("IP address is invalid")
   374  
   375  	case RegisteredDomainFieldName:
   376  		err := policy.ValidDomain(fieldValue)
   377  		if err != nil {
   378  			return fmt.Errorf("registered domain name is invalid")
   379  		}
   380  		suffix, err := iana.ExtractSuffix(fieldValue)
   381  		if err != nil {
   382  			return fmt.Errorf("registered domain name is invalid")
   383  		}
   384  		if fieldValue == suffix {
   385  			return fmt.Errorf("registered domain name cannot be a bare top-level domain")
   386  		}
   387  		base := strings.TrimSuffix(fieldValue, "."+suffix)
   388  		if base == "" || strings.Contains(base, ".") {
   389  			return fmt.Errorf("only the eTLD+1 (e.g., example.com or example.co.uk) should be provided")
   390  		}
   391  		return nil
   392  
   393  	case AccountURIFieldName:
   394  		// Validation here is nuanced: we accept a well-formed Let's Encrypt
   395  		// Account URI even though the prefix may vary. We don't store the URI
   396  		// in the override; we only verify its shape and extract the Account ID.
   397  		// Requesting this value in a format that most clients actually expose
   398  		// allows us to reliably obtain a valid Account ID while ensuring the
   399  		// URI targets Let's Encrypt rather than some other ACME CA.
   400  		u, err := url.Parse(fieldValue)
   401  		if err != nil {
   402  			return fmt.Errorf("account URI is not a valid URL")
   403  		}
   404  		if !strings.HasSuffix(u.Host, "api.letsencrypt.org") || !strings.HasPrefix(u.Path, "/acme/acct/") {
   405  			return fmt.Errorf("account URI is invalid")
   406  		}
   407  		segments := strings.Split(strings.Trim(u.Path, "/"), "/")
   408  		if len(segments) != 3 || segments[0] != "acme" || segments[1] != "acct" {
   409  			return fmt.Errorf("account URI path must be of the form /acme/acct/{id}")
   410  		}
   411  		_, err = strconv.ParseUint(segments[2], 10, 64)
   412  		if err != nil {
   413  			return fmt.Errorf("account ID must be a positive integer")
   414  		}
   415  		return nil
   416  
   417  	case TierFieldName:
   418  		valids, ok := tierOptionsByRateLimit[rateLimit]
   419  		if !ok {
   420  			return fmt.Errorf("unknown rate limit name: %s", rateLimit)
   421  		}
   422  		if slices.Contains(valids, fieldValue) {
   423  			return nil
   424  		}
   425  		return fmt.Errorf("invalid request override quantity, valid options are: %s", strings.Join(valids, ", "))
   426  	}
   427  	return fmt.Errorf("unknown field %q", fieldName)
   428  }
   429  
   430  func setOverrideRequestFormHeaders(w http.ResponseWriter) {
   431  	// Prevent this page from being embedded in a frame/iframe to mitigate
   432  	// clickjacking.
   433  	w.Header().Set("X-Frame-Options", "DENY")
   434  
   435  	w.Header().Set("Content-Security-Policy", strings.Join([]string{
   436  		// Only allow same-origin and HTTPS subresources by default.
   437  		"default-src 'self' https:",
   438  		// Only allow scripts from same-origin (no inline/eval).
   439  		"script-src 'self'",
   440  		// Only allow stylesheets from same-origin.
   441  		"style-src 'self'",
   442  		// Block legacy plugin content (<object>, <embed>, <applet>).
   443  		"object-src 'none'",
   444  		// Do not allow any site to frame this page.
   445  		"frame-ancestors 'none'",
   446  	}, "; "))
   447  
   448  	// Mitigates cross-origin window references/leaks.
   449  	w.Header().Set("Cross-Origin-Opener-Policy", "same-origin")
   450  
   451  	// Restrict other sites from embedding/fetching this resource.
   452  	w.Header().Set("Cross-Origin-Resource-Policy", "same-site")
   453  
   454  	// Prevent caching of this page and its responses.
   455  	w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0")
   456  	w.Header().Set("Pragma", "no-cache")
   457  	w.Header().Set("Expires", "0")
   458  }
   459  
   460  // makeOverrideRequestFormHandler is a wrapper around the overrideRequestHandler
   461  // method that allows it to be used as an http.HandlerFunc.
   462  func (sfe *SelfServiceFrontEndImpl) makeOverrideRequestFormHandler(formHTML template.HTML, rateLimit, displayRateLimit string) http.HandlerFunc {
   463  	return func(w http.ResponseWriter, r *http.Request) {
   464  		sfe.overrideRequestHandler(w, formHTML, rateLimit, displayRateLimit)
   465  	}
   466  }
   467  
   468  // overrideRequestHandler renders the override request form with the specified
   469  // form HTML and rate limit. RateLimit is the limit that will be used to
   470  // validate the form fields when the user submits the form. RateLimitForDisplay
   471  // is the limit that will be displayed to the user in the form. These are
   472  // typically the same, but can differ in cases where multiple forms are used for
   473  // the same rate limit.
   474  func (sfe *SelfServiceFrontEndImpl) overrideRequestHandler(w http.ResponseWriter, formHTML template.HTML, rateLimit, displayRateLimit string) {
   475  	setOverrideRequestFormHeaders(w)
   476  	sfe.renderTemplate(w, "overrideForm.html", map[string]any{
   477  		"FormHTML":                    formHTML,
   478  		"RateLimit":                   rateLimit,
   479  		"DisplayRateLimit":            displayRateLimit,
   480  		"ValidateFieldPath":           overridesValidateField,
   481  		"SubmitRequestPath":           overridesSubmitRequest,
   482  		"AutoApprovedSuccessPath":     overridesAutoApprovedSuccess,
   483  		"RequestSubmittedSuccessPath": overridesRequestSubmittedSuccess,
   484  	})
   485  }
   486  
   487  type validationRequest struct {
   488  	RateLimit string `json:"rateLimit"`
   489  	Field     string `json:"field"`
   490  	Value     string `json:"value"`
   491  }
   492  
   493  type validationResponse struct {
   494  	Field string `json:"field"`
   495  	Valid bool   `json:"valid"`
   496  	Error string `json:"error,omitempty"`
   497  }
   498  
   499  // validateOverrideFieldHandler validates the provided field and value against
   500  // the specified rate limit. It returns a JSON response indicating whether the
   501  // field is valid, and an error message if it is not.
   502  func (sfe *SelfServiceFrontEndImpl) validateOverrideFieldHandler(w http.ResponseWriter, r *http.Request) {
   503  	var req validationRequest
   504  	err := json.NewDecoder(http.MaxBytesReader(w, r.Body, validateOverrideFieldBodyLimit)).Decode(&req)
   505  	if err != nil {
   506  		sfe.log.Errf("failed to decode validation request: %s", err)
   507  		http.Error(w, "bad request", http.StatusBadRequest)
   508  		return
   509  	}
   510  	valid := true
   511  	var message string
   512  	err = validateOverrideRequestField(req.Field, req.Value, req.RateLimit)
   513  	if err != nil {
   514  		valid = false
   515  		message = err.Error()
   516  	}
   517  
   518  	w.Header().Set("Content-Type", "application/json")
   519  	err = json.NewEncoder(w).Encode(validationResponse{
   520  		Field: req.Field,
   521  		Valid: valid,
   522  		Error: message,
   523  	})
   524  	if err != nil {
   525  		sfe.log.Errf("failed to encode validation response: %s", err)
   526  		http.Error(w, "failed to encode validation response", http.StatusInternalServerError)
   527  		return
   528  	}
   529  }
   530  
   531  // overrideAutoApprovedSuccessHandler renders the success page after a
   532  // successful override request submission which was automatically approved.
   533  func (sfe *SelfServiceFrontEndImpl) overrideAutoApprovedSuccessHandler(w http.ResponseWriter, r *http.Request) {
   534  	sfe.renderTemplate(w, "overrideAutoApprovedSuccess.html", nil)
   535  }
   536  
   537  // overrideRequestSubmittedSuccessHandler renders the success page after a
   538  // successful override request submission created a Zendesk ticket for manual
   539  // review.
   540  func (sfe *SelfServiceFrontEndImpl) overrideRequestSubmittedSuccessHandler(w http.ResponseWriter, r *http.Request) {
   541  	sfe.renderTemplate(w, "overrideRequestSubmittedSuccess.html", nil)
   542  }
   543  
   544  type overrideRequest struct {
   545  	RateLimit string            `json:"rateLimit"`
   546  	Fields    map[string]string `json:"fields"`
   547  }
   548  
   549  // submitOverrideRequestHandler handles the submission of override requests. It
   550  // expects a POST request with a JSON payload (overrideRequest). It validates
   551  // each of the form fields and either:
   552  //
   553  //	a. auto-approves the override request and returns 201 Created, or
   554  //	b. creates a Zendesk ticket for manual review, and returns 202 Accepted, or
   555  //	c. encounters an error and returns an appropriate 4xx or 5xx status code.
   556  //
   557  // The JavaScript frontend is configured to validate the form fields twice: once
   558  // when the requester inputs data, and once more just before submitting the
   559  // form. Any validation errors returned by this handler are an indication that
   560  // either the form logic is flawed or the requester has bypassed the form and
   561  // submitting (malformed) requests directly to this endpoint.
   562  func (sfe *SelfServiceFrontEndImpl) submitOverrideRequestHandler(w http.ResponseWriter, r *http.Request) {
   563  	var refundLimits func()
   564  	if sfe.limiter != nil && sfe.txnBuilder != nil {
   565  		requesterIP, err := web.ExtractRequesterIP(r)
   566  		if err != nil {
   567  			sfe.log.Errf("determining requester IP address: %s", err)
   568  			http.Error(w, "failed to determine the IP address of the requester", http.StatusInternalServerError)
   569  			return
   570  		}
   571  
   572  		txns, err := sfe.txnBuilder.LimitOverrideRequestsPerIPAddressTransaction(requesterIP)
   573  		if err != nil {
   574  			sfe.log.Errf("building transaction for override request form limits: %s", err)
   575  			http.Error(w, "failed to build transaction for override request form limits", http.StatusInternalServerError)
   576  			return
   577  		}
   578  
   579  		d, err := sfe.limiter.Spend(r.Context(), txns)
   580  		if err != nil {
   581  			sfe.log.Errf("spending transaction for override request form limits: %s", err)
   582  			http.Error(w, "failed to spend transaction for override request form limits", http.StatusInternalServerError)
   583  			return
   584  		}
   585  
   586  		err = d.Result(sfe.clk.Now())
   587  		if err != nil {
   588  			var bErr *berrors.BoulderError
   589  			if errors.As(err, &bErr) && bErr.Type == berrors.RateLimit {
   590  				http.Error(w, bErr.Detail, http.StatusTooManyRequests)
   591  				return
   592  			}
   593  			sfe.log.Errf("determining result of override request form limits transaction: %s", err)
   594  			http.Error(w, "failed to determine result of override request form limits transaction", http.StatusInternalServerError)
   595  			return
   596  		}
   597  
   598  		refundLimits = func() {
   599  			_, err := sfe.limiter.Refund(r.Context(), txns)
   600  			if err != nil {
   601  				sfe.log.Errf("refunding transaction for override request form limits: %s", err)
   602  			}
   603  		}
   604  	}
   605  	var overrideRequestHandled bool
   606  	defer func() {
   607  		if !overrideRequestHandled && refundLimits != nil {
   608  			refundLimits()
   609  		}
   610  	}()
   611  
   612  	var req overrideRequest
   613  	err := json.NewDecoder(http.MaxBytesReader(w, r.Body, submitOverrideRequestBodyLimit)).Decode(&req)
   614  	if err != nil {
   615  		sfe.log.Errf("failed to decode override request: %s", err)
   616  		http.Error(w, "bad request", http.StatusBadRequest)
   617  		return
   618  	}
   619  	if req.RateLimit == "" {
   620  		http.Error(w, "rate limit not specified", http.StatusBadRequest)
   621  		return
   622  	}
   623  
   624  	getValidated := func(name string) (string, error) {
   625  		val := strings.TrimSpace(req.Fields[name])
   626  		err := validateOverrideRequestField(name, val, req.RateLimit)
   627  		if err != nil {
   628  			return "", fmt.Errorf("invalid field %q: %w", name, err)
   629  		}
   630  		return val, nil
   631  	}
   632  
   633  	var validFields = make(map[string]string)
   634  	for _, name := range []string{
   635  		// Note: not all of these fields will be included in the Zendesk ticket,
   636  		// but they are all required for the submission to be considered valid.
   637  		subscriberAgreementFieldName,
   638  		privacyPolicyFieldName,
   639  		mailingListFieldName,
   640  		fundraisingFieldName,
   641  		emailAddressFieldName,
   642  		OrganizationFieldName,
   643  		useCaseFieldName,
   644  		TierFieldName,
   645  	} {
   646  		val, err := getValidated(name)
   647  		if err != nil {
   648  			http.Error(w, err.Error(), http.StatusBadRequest)
   649  			return
   650  		}
   651  		validFields[name] = val
   652  	}
   653  
   654  	autoApproveOverride := func(ctx context.Context, rateLimitFieldValue string, fields map[string]string) bool {
   655  		if !sfe.autoApproveOverrides {
   656  			return false
   657  		}
   658  		req, _, err := makeAddOverrideRequest(rateLimitFieldValue, fields)
   659  		if err != nil {
   660  			sfe.log.Errf("failed to create automatically approved override request: %s", err)
   661  			return false
   662  		}
   663  		resp, err := sfe.ra.AddRateLimitOverride(ctx, req)
   664  		if err != nil {
   665  			sfe.log.Errf("failed to create automatically approved override request: %s", err)
   666  			return false
   667  		}
   668  		return resp.Enabled
   669  	}
   670  
   671  	switch req.RateLimit {
   672  	case rl.NewOrdersPerAccount.String():
   673  		accountURI, err := getValidated(AccountURIFieldName)
   674  		if err != nil {
   675  			http.Error(w, err.Error(), http.StatusBadRequest)
   676  			return
   677  		}
   678  		validFields[AccountURIFieldName] = accountURI
   679  
   680  		if validFields[TierFieldName] == newOrdersPerAccountTierOptions[0] {
   681  			overrideRequestHandled = autoApproveOverride(r.Context(), req.RateLimit, validFields)
   682  		}
   683  
   684  	case rl.CertificatesPerDomainPerAccount.String():
   685  		accountURI, err := getValidated(AccountURIFieldName)
   686  		if err != nil {
   687  			http.Error(w, err.Error(), http.StatusBadRequest)
   688  			return
   689  		}
   690  		validFields[AccountURIFieldName] = accountURI
   691  
   692  		if validFields[TierFieldName] == certificatesPerDomainPerAccountTierOptions[0] {
   693  			overrideRequestHandled = autoApproveOverride(r.Context(), req.RateLimit, validFields)
   694  		}
   695  
   696  	case rl.CertificatesPerDomain.String() + perDNSNameSuffix:
   697  		registeredDomain, err := getValidated(RegisteredDomainFieldName)
   698  		if err != nil {
   699  			http.Error(w, err.Error(), http.StatusBadRequest)
   700  			return
   701  		}
   702  		validFields[RegisteredDomainFieldName] = registeredDomain
   703  
   704  		if validFields[TierFieldName] == certificatesPerDomainTierOptions[0] {
   705  			overrideRequestHandled = autoApproveOverride(r.Context(), req.RateLimit, validFields)
   706  		}
   707  
   708  	case rl.CertificatesPerDomain.String() + perIPSuffix:
   709  		ipAddress, err := getValidated(IPAddressFieldName)
   710  		if err != nil {
   711  			http.Error(w, err.Error(), http.StatusBadRequest)
   712  			return
   713  		}
   714  		validFields[IPAddressFieldName] = ipAddress
   715  
   716  		if validFields[TierFieldName] == certificatesPerDomainTierOptions[0] {
   717  			overrideRequestHandled = autoApproveOverride(r.Context(), req.RateLimit, validFields)
   718  		}
   719  
   720  	default:
   721  		http.Error(w, "unknown rate limit", http.StatusBadRequest)
   722  		return
   723  	}
   724  
   725  	if sfe.ee != nil && validFields[mailingListFieldName] == "true" {
   726  		_, err := sfe.ee.SendContacts(r.Context(), &emailpb.SendContactsRequest{Emails: []string{validFields[emailAddressFieldName]}})
   727  		if err != nil {
   728  			sfe.log.Errf("sending contact to email-exporter: %s", err)
   729  		}
   730  	}
   731  
   732  	if sfe.ee != nil && validFields[fundraisingFieldName] == fundraisingYesOption {
   733  		_, err := sfe.ee.SendCase(r.Context(), &emailpb.SendCaseRequest{
   734  			Origin:        "Web",
   735  			Subject:       fmt.Sprintf("%s rate limit override request for %s", req.RateLimit, validFields[OrganizationFieldName]),
   736  			ContactEmail:  validFields[emailAddressFieldName],
   737  			Organization:  validFields[OrganizationFieldName],
   738  			RateLimitName: req.RateLimit,
   739  			RateLimitTier: validFields[TierFieldName],
   740  			UseCase:       validFields[useCaseFieldName],
   741  		})
   742  		if err != nil {
   743  			sfe.log.Errf("sending case to email-exporter: %s", err)
   744  		}
   745  	}
   746  
   747  	if overrideRequestHandled {
   748  		sfe.log.Infof("automatically approved override request for %s", validFields[OrganizationFieldName])
   749  		w.WriteHeader(http.StatusCreated)
   750  		return
   751  	}
   752  
   753  	ticketID, err := createOverrideRequestZendeskTicket(
   754  		sfe.zendeskClient,
   755  		req.RateLimit,
   756  		validFields[emailAddressFieldName],
   757  		validFields[useCaseFieldName],
   758  		validFields[OrganizationFieldName],
   759  		validFields[TierFieldName],
   760  
   761  		// Only one of these will be non-empty, depending on the
   762  		// rateLimitField value.
   763  		validFields[AccountURIFieldName],
   764  		validFields[RegisteredDomainFieldName],
   765  		validFields[IPAddressFieldName],
   766  	)
   767  	if err != nil {
   768  		sfe.log.Errf("failed to create override request Zendesk ticket: %s", err)
   769  		http.Error(w, "failed to create support ticket", http.StatusInternalServerError)
   770  		return
   771  	}
   772  
   773  	// If we got here the request has either been auto-approved or a Zendesk
   774  	// ticket has been created for manual review, so a refund is not needed.
   775  	overrideRequestHandled = true
   776  	sfe.log.Infof("created override request Zendesk ticket %d", ticketID)
   777  	w.WriteHeader(http.StatusAccepted)
   778  }