Skip to content

Commit

Permalink
use enum for better representation
Browse files Browse the repository at this point in the history
add support for allowed origins

POC adding single handler for C-S-S
  • Loading branch information
Shane Osbourne committed Apr 5, 2023
1 parent ed1ff65 commit 2685c2a
Show file tree
Hide file tree
Showing 2 changed files with 152 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,43 @@ import WebKit
import Combine
import ContentScopeScripts
import UserScript
import Common
import os.log

public enum AllowedOrigin {
case etldPlus1(hostname: String)
case exact(hostname: String)
}

public enum AllowedOrigins {
case all;
case only(_: [AllowedOrigin])

public func isAllowed(_ origin: String) -> Bool {
switch self {
case .all: return true
case .only(let allowed):
return allowed.contains { allowed in
switch allowed {
// exact match
case .exact(hostname: let hostname):
return hostname == origin
// etldPlus1, like duckduckgo.com to match dev.duckduckgo.com + duckduckgo.com
case .etldPlus1(hostname: let hostname):
return false // todo
}
}
}
}
}


public protocol ContentScopeScriptsSubFeature {
typealias MessageReplyHandler = (String?) -> Void
typealias MessageHandlerSubFeature = ([String: Any], @escaping MessageReplyHandler) -> Void
var allowedOrigins: AllowedOrigins { get }
func messageHandlerForFeature(_ messageName: String) -> MessageHandlerSubFeature?;
}

public final class ContentScopeProperties: Encodable {
public let globalPrivacyControlValue: Bool
Expand All @@ -38,9 +75,9 @@ public final class ContentScopeProperties: Encodable {
}
}
public struct ContentScopeFeature: Encodable {

public let settings: [String: ContentScopeFeatureToggles]

public init(featureToggles: ContentScopeFeatureToggles) {
self.settings = ["featureToggles": featureToggles]
}
Expand All @@ -49,18 +86,18 @@ public struct ContentScopeFeature: Encodable {
public struct ContentScopeFeatureToggles: Encodable {

public let emailProtection: Bool

public let credentialsAutofill: Bool
public let identitiesAutofill: Bool
public let creditCardsAutofill: Bool

public let credentialsSaving: Bool

public let passwordGeneration: Bool

public let inlineIconCredentials: Bool
public let thirdPartyCredentialsProvider: Bool

// Explicitly defined memberwise init only so it can be public
public init(emailProtection: Bool,
credentialsAutofill: Bool,
Expand All @@ -70,7 +107,7 @@ public struct ContentScopeFeatureToggles: Encodable {
passwordGeneration: Bool,
inlineIconCredentials: Bool,
thirdPartyCredentialsProvider: Bool) {

self.emailProtection = emailProtection
self.credentialsAutofill = credentialsAutofill
self.identitiesAutofill = identitiesAutofill
Expand All @@ -80,18 +117,18 @@ public struct ContentScopeFeatureToggles: Encodable {
self.inlineIconCredentials = inlineIconCredentials
self.thirdPartyCredentialsProvider = thirdPartyCredentialsProvider
}

enum CodingKeys: String, CodingKey {
case emailProtection = "emailProtection"

case credentialsAutofill = "inputType_credentials"
case identitiesAutofill = "inputType_identities"
case creditCardsAutofill = "inputType_creditCards"

case credentialsSaving = "credentials_saving"

case passwordGeneration = "password_generation"

case inlineIconCredentials = "inlineIcon_credentials"
case thirdPartyCredentialsProvider = "third_party_credentials_provider"
}
Expand All @@ -107,13 +144,77 @@ public struct ContentScopePlatform: Encodable {
#endif
}

public final class ContentScopeUserScript: NSObject, UserScript {
public let messageNames: [String] = []
public final class ContentScopeUserScript: NSObject, UserScript, UserScriptMessageEncryption {
public typealias MyCallback = (_ messageName: String) -> MessageHandlerSubFeature?
public let encrypter: UserScriptEncrypter
public let hostProvider: UserScriptHostProvider
public let generatedSecret: String = UUID().uuidString

var callbacks: [SubFeatureName: ContentScopeScriptsSubFeature] = [:]

public func registerSubFeatureFor(name: SubFeatureName, delegate: ContentScopeScriptsSubFeature) {
callbacks[name] = delegate
}

public let messageNames: [String] = [
"contentScopeScriptsMessage"
]

public enum SubFeatureName: String, CaseIterable {
case duckplayer
}

public init(_ privacyConfigManager: PrivacyConfigurationManaging, properties: ContentScopeProperties) {
public init(_ privacyConfigManager: PrivacyConfigurationManaging,
properties: ContentScopeProperties,
encrypter: UserScriptEncrypter = AESGCMUserScriptEncrypter(),
hostProvider: UserScriptHostProvider = SecurityOriginHostProvider()
) {
self.hostProvider = hostProvider
self.encrypter = encrypter
source = ContentScopeUserScript.generateSource(privacyConfigManager, properties: properties)
}

public func messageHandlerFor(_ message: WKScriptMessage) -> (MessageHandlerSubFeature, [String: Any])? {

/// first, check that the incoming message is roughly in the correct shape
guard let dict = message.messageBody as? [String: Any],
let featureName = dict["featureName"] as? String,
let name = dict["name"] as? String,
let data = dict["data"] as? [String: Any] else {
os_log("The incoming message was not valid - one or more of 'featureName', 'name' or 'data' was missing")
return nil;
}

/// Now try to match the message to a registered delegate
guard let subFeature = SubFeatureName(rawValue: featureName),
let delegate = callbacks[subFeature] else {
os_log("The incoming message had `%@` as the `featureName`, but that didn't match any registered delegates", featureName);
let keyString = callbacks.keys.map { $0.rawValue }.joined(separator: ", ")
os_log("\tavailable delegates were: %@", keyString);
return nil;
}

/// Check if the selected delegate accepts messages from this origin
guard delegate.allowedOrigins.isAllowed(hostProvider.hostForMessage(message)) else {
os_log("the incoming message is ignored because it originated from an origin that feature `%@` doesnt support", subFeature.rawValue)
return nil;
}

/// Now ask the delegate to provide the handler
guard let handler = delegate.messageHandlerForFeature(name) else {
os_log("the incoming message is ignored because the feature `%@` couldn't provide a handler for message named `%@`", subFeature.rawValue, name)
return nil
}

/// if we get this far:
/// 1) it's a valid message
/// 2) we matched a registered delegate
/// 3) the delegate provided a handler
/// 4) so, we can return the handler along with the message payload and continue
let output: (MessageHandlerSubFeature, [String: Any]) = (handler, data);
return output
}

public static func generateSource(_ privacyConfigurationManager: PrivacyConfigurationManaging, properties: ContentScopeProperties) -> String {

guard let privacyConfigJson = String(data: privacyConfigurationManager.currentConfig, encoding: .utf8),
Expand All @@ -124,21 +225,42 @@ public final class ContentScopeUserScript: NSObject, UserScript {
else {
return ""
}


// print("✅ \(String(describing: ContentScopeScripts.Bundle.url(forResource: "index", withExtension: "html", subdirectory: "pages/duckplayer")))")
return loadJS("contentScope", from: ContentScopeScripts.Bundle, withReplacements: [
"$CONTENT_SCOPE$": privacyConfigJson,
"$USER_UNPROTECTED_DOMAINS$": userUnprotectedDomainsString,
"$USER_PREFERENCES$": jsonPropertiesString
])
}

public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
}

public let source: String

public let injectionTime: WKUserScriptInjectionTime = .atDocumentStart
public let forMainFrameOnly: Bool = false
public let requiresRunInPageContentWorld: Bool = true

}

@available(macOS 11, *)
extension ContentScopeUserScript: WKScriptMessageHandlerWithReply {

public func userContentController(_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage,
replyHandler: @escaping (Any?, String?) -> Void) {
guard let (messageHandler, data) = messageHandlerFor(message) else {
// Unsupported message fail silently
return
}

messageHandler(data) {
replyHandler($0, nil)
}
}
}

// MARK: - Fallback for macOS 10.15
extension ContentScopeUserScript: WKScriptMessageHandler {
public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
processEncryptedMessage(message, from: userContentController)
}
}
9 changes: 9 additions & 0 deletions Sources/UserScript/UserScriptMessageEncryption.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ public protocol UserScriptMessageEncryption {

typealias MessageReplyHandler = (String?) -> Void
typealias MessageHandler = (UserScriptMessage, @escaping MessageReplyHandler) -> Void
typealias MessageHandlerSubFeature = ([String: Any], @escaping MessageReplyHandler) -> Void

var encrypter: UserScriptEncrypter { get }
var generatedSecret: String { get }

func messageHandlerFor(_ messageName: String) -> MessageHandler?
func messageHandlerFor(_ message: WKScriptMessage) -> (MessageHandlerSubFeature, [String: Any])?
func processEncryptedMessage(_ message: UserScriptMessage, from userContentController: WKUserContentController)
}

Expand Down Expand Up @@ -78,4 +80,11 @@ extension UserScriptMessageEncryption {
message.messageWebView?.evaluateJavaScript(script)
}
}

public func messageHandlerFor(_ messageName: String) -> MessageHandler? {
return nil
}
public func messageHandlerFor(_ message: WKScriptMessage) -> (MessageHandlerSubFeature, [String: Any])? {
return nil
}
}

0 comments on commit 2685c2a

Please sign in to comment.