Commit 6f8a441b62
Unsigned
Layout: unified · split
DomainDig/IntegrationService.swift +18 −1
| @@ -40,6 +40,7 @@ final class IntegrationService { | ||
| 40 | 40 | switch updatedTarget.configuration { |
| 41 | 41 | case .webhook(var configuration): |
| 42 | 42 | if let webhookURL { |
| 43 | try Self.validateHTTPS(webhookURL) | |
| 43 | 44 | let reference = configuration.credentialReference ?? Self.secretReference(for: updatedTarget.id, suffix: "webhook") |
| 44 | 45 | try IntegrationSecretStore.save(secret: webhookURL, reference: reference) |
| 45 | 46 | configuration.credentialReference = reference |
| @@ -48,6 +49,7 @@ final class IntegrationService { | ||
| 48 | 49 | } |
| 49 | 50 | case .slack(var configuration): |
| 50 | 51 | if let slackWebhookURL { |
| 52 | try Self.validateHTTPS(slackWebhookURL) | |
| 51 | 53 | let reference = configuration.credentialReference ?? Self.secretReference(for: updatedTarget.id, suffix: "slack") |
| 52 | 54 | try IntegrationSecretStore.save(secret: slackWebhookURL, reference: reference) |
| 53 | 55 | configuration.credentialReference = reference |
| @@ -429,6 +431,15 @@ final class IntegrationService { | ||
| 429 | 431 | "integration.\(integrationID.uuidString).\(suffix)" |
| 430 | 432 | } |
| 431 | 433 | |
| 434 | private static func validateHTTPS(_ string: String) throws { | |
| 435 | guard let url = URL(string: string) else { | |
| 436 | throw IntegrationError.invalidURL | |
| 437 | } | |
| 438 | guard url.scheme?.lowercased() == "https" else { | |
| 439 | throw IntegrationError.insecureURL | |
| 440 | } | |
| 441 | } | |
| 442 | ||
| 432 | 443 | private static func loadTargets(defaults: UserDefaults) -> [IntegrationTarget] { |
| 433 | 444 | load([IntegrationTarget].self, key: StorageKey.targets, defaults: defaults) ?? [] |
| 434 | 445 | } |
| @@ -524,6 +535,7 @@ private struct SlackText: Encodable { | ||
| 524 | 535 | |
| 525 | 536 | private enum IntegrationError: LocalizedError { |
| 526 | 537 | case invalidURL |
| 538 | case insecureURL | |
| 527 | 539 | case missingSecret |
| 528 | 540 | case invalidResponse(Int) |
| 529 | 541 | case invalidSMTPPort |
| @@ -534,6 +546,8 @@ private enum IntegrationError: LocalizedError { | ||
| 534 | 546 | switch self { |
| 535 | 547 | case .invalidURL: |
| 536 | 548 | return "The integration URL is invalid." |
| 549 | case .insecureURL: | |
| 550 | return "The integration URL must use https. A webhook URL is itself a secret, so http would send it in cleartext." | |
| 537 | 551 | case .missingSecret: |
| 538 | 552 | return "This integration is missing a saved secret." |
| 539 | 553 | case .invalidResponse(let statusCode): |
| @@ -555,9 +569,12 @@ private enum HTTPIntegrationClient { | ||
| 555 | 569 | headers: [String: String], |
| 556 | 570 | timeoutSeconds: Double |
| 557 | 571 | ) async throws { |
| 558 | guard let url = URL(string: urlString), url.scheme?.lowercased() == "https" else { | |
| 572 | guard let url = URL(string: urlString) else { | |
| 559 | 573 | throw IntegrationError.invalidURL |
| 560 | 574 | } |
| 575 | guard url.scheme?.lowercased() == "https" else { | |
| 576 | throw IntegrationError.insecureURL | |
| 577 | } | |
| 561 | 578 | |
| 562 | 579 | var request = URLRequest(url: url, timeoutInterval: timeoutSeconds) |
| 563 | 580 | request.httpMethod = "POST" |