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 }