Преглед изворни кода

Restrict embedded Google browsing to sign-in only.

Block Gmail, Help, and other Google apps in the Indeed web view while allowing OAuth, and inject prompt=select_account so users can choose which Google account to use.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 пре 2 месеци
родитељ
комит
f9d2da77ac
1 измењених фајлова са 151 додато и 2 уклоњено
  1. 151 2
      App for Indeed/Controllers/IndeedJobBrowserWindowController.swift

+ 151 - 2
App for Indeed/Controllers/IndeedJobBrowserWindowController.swift

@@ -185,14 +185,163 @@ final class IndeedJobBrowserViewController: NSViewController, WKNavigationDelega
         updateNavigationButtons()
     }
 
+    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
+        guard IndeedWebBrowsingPolicy.allows(navigationAction: navigationAction) else {
+            decisionHandler(.cancel)
+            return
+        }
+
+        let isMainFrame = navigationAction.targetFrame?.isMainFrame ?? true
+        if isMainFrame,
+           let url = navigationAction.request.url,
+           IndeedWebBrowsingPolicy.shouldForceGoogleAccountPicker(for: url) {
+            let pickerURL = IndeedWebBrowsingPolicy.urlForcingGoogleAccountPicker(url)
+            if pickerURL != url {
+                webView.load(URLRequest(url: pickerURL))
+                decisionHandler(.cancel)
+                return
+            }
+        }
+
+        decisionHandler(.allow)
+    }
+
     /// Target=_blank / `window.open` without a frame: load in this view so apply flows stay in-app.
     func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
-        if navigationAction.targetFrame == nil {
-            webView.load(navigationAction.request)
+        guard navigationAction.targetFrame == nil,
+              IndeedWebBrowsingPolicy.allows(navigationAction: navigationAction) else {
+            return nil
         }
+        var request = navigationAction.request
+        if let url = request.url,
+           IndeedWebBrowsingPolicy.shouldForceGoogleAccountPicker(for: url) {
+            request = URLRequest(url: IndeedWebBrowsingPolicy.urlForcingGoogleAccountPicker(url))
+        }
+        webView.load(request)
         return nil
     }
 
     /// Desktop Safari UA helps Indeed serve a full desktop apply experience.
     private static let desktopSafariLikeUserAgent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4 Safari/605.1.15"
 }
+
+// MARK: - Google sign-in only (no Gmail, Help hub, Search, etc.)
+
+/// Limits embedded browsing on Google-owned hosts to OAuth / sign-in, while leaving Indeed and third-party apply sites unrestricted.
+enum IndeedWebBrowsingPolicy {
+    static func allows(navigationAction: WKNavigationAction) -> Bool {
+        guard let url = navigationAction.request.url else { return true }
+        let isMainFrame = navigationAction.targetFrame?.isMainFrame ?? true
+        if isMainFrame {
+            return allowsMainFrameNavigation(to: url)
+        }
+        return allowsSubframeNavigation(to: url)
+    }
+
+    static func allowsMainFrameNavigation(to url: URL) -> Bool {
+        guard let scheme = url.scheme?.lowercased() else { return false }
+        if scheme == "about" || scheme == "blob" || scheme == "data" { return true }
+        guard scheme == "http" || scheme == "https" else { return false }
+        guard let host = url.host?.lowercased() else { return false }
+
+        if !isGooglePropertyHost(host) {
+            return true
+        }
+        return isAllowedGoogleSignInHost(host, path: url.path)
+    }
+
+    static func allowsSubframeNavigation(to url: URL) -> Bool {
+        guard let scheme = url.scheme?.lowercased() else { return false }
+        if scheme == "about" || scheme == "blob" || scheme == "data" { return true }
+        guard scheme == "http" || scheme == "https" else { return false }
+        guard let host = url.host?.lowercased() else { return false }
+
+        if !isGooglePropertyHost(host) {
+            return true
+        }
+        if isAllowedGoogleSignInHost(host, path: url.path) {
+            return true
+        }
+        let path = url.path.lowercased()
+        if host == "www.google.com", path.contains("/recaptcha") {
+            return true
+        }
+        if host == "gstatic.com" || host.hasSuffix(".gstatic.com") {
+            return true
+        }
+        return false
+    }
+
+    private static func isGooglePropertyHost(_ host: String) -> Bool {
+        if host == "google.com" || host.hasSuffix(".google.com") { return true }
+        if host == "gmail.com" || host.hasSuffix(".gmail.com") { return true }
+        if host == "googleusercontent.com" || host.hasSuffix(".googleusercontent.com") { return true }
+        if host == "gstatic.com" || host.hasSuffix(".gstatic.com") { return true }
+        if host == "youtube.com" || host.hasSuffix(".youtube.com") { return true }
+        if host == "blogger.com" || host.hasSuffix(".blogger.com") { return true }
+        if host == "withgoogle.com" || host.hasSuffix(".withgoogle.com") { return true }
+        return false
+    }
+
+    /// True for Google OAuth / sign-in navigations where we inject `prompt=select_account`.
+    static func shouldForceGoogleAccountPicker(for url: URL) -> Bool {
+        guard let host = url.host?.lowercased(),
+              host == "accounts.google.com" || host.hasPrefix("accounts.google.") else {
+            return false
+        }
+        let path = url.path.lowercased()
+        if path.contains("oauth")
+            || path.contains("accountchooser")
+            || path.contains("/gsi/")
+            || path.hasPrefix("/signin/") {
+            return true
+        }
+        let query = url.query?.lowercased() ?? ""
+        return query.contains("client_id=")
+    }
+
+    /// Rewrites Google sign-in URLs so the account chooser is always shown (not silent reuse of the last account).
+    static func urlForcingGoogleAccountPicker(_ url: URL) -> URL {
+        guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false) else { return url }
+        var items = components.queryItems ?? []
+
+        items.removeAll { $0.name.compare("login_hint", options: .caseInsensitive) == .orderedSame }
+        items.removeAll { $0.name.compare("hint", options: .caseInsensitive) == .orderedSame }
+
+        if let promptIndex = items.firstIndex(where: { $0.name.compare("prompt", options: .caseInsensitive) == .orderedSame }) {
+            let existing = items[promptIndex].value ?? ""
+            if !existing.lowercased().contains("select_account") {
+                let merged = existing.isEmpty ? "select_account" : "\(existing) select_account"
+                items[promptIndex] = URLQueryItem(name: "prompt", value: merged)
+            }
+        } else {
+            items.append(URLQueryItem(name: "prompt", value: "select_account"))
+        }
+
+        components.queryItems = items.isEmpty ? nil : items
+        return components.url ?? url
+    }
+
+    /// Hosts used during “Sign in with Google” — not Help Center, Gmail, Drive, Search, or the apps launcher.
+    private static func isAllowedGoogleSignInHost(_ host: String, path: String) -> Bool {
+        if host == "accounts.google.com" || host.hasPrefix("accounts.google.") {
+            return true
+        }
+        if host == "signin.google.com" {
+            return true
+        }
+        if host == "oauth.googleusercontent.com" {
+            return true
+        }
+        if host == "googleusercontent.com" || host.hasSuffix(".googleusercontent.com") {
+            return true
+        }
+        if host == "policies.google.com" || host == "privacy.google.com" {
+            return true
+        }
+        if host == "apis.google.com" {
+            return true
+        }
+        return false
+    }
+}