From 066eb6323bbb12804a930538dbd3bf837dde0c60 Mon Sep 17 00:00:00 2001 From: Ryan Tobin Date: Thu, 14 Dec 2023 15:48:12 -0800 Subject: [PATCH] Implement and Enable Redacted Events Manager Summary: There are certain events that are "sensitive" that violate our terms that are transmitted from 3P clients to Meta. We want to redact these event names from ever being sent from the client-side. In order to do this, we need to retrieve a list of these "redacted" events from the server-side and build the ```RedactedEventsManager``` to process and redact these events. More information can be seen [here](https://docs.google.com/document/d/1EYy0vZ-v6q1QDGEDj_N2is6QbX4eDPodI1lYz6W8GEU/), [here](https://docs.google.com/document/d/1OcrMzjYloBgFmNRgiJ3C_beMtuKxbEr8KII0YWya8Pw/), and [here](https://docs.google.com/document/d/1OhJ-LSX0fl8WJJlbs17ka3wleUQmUDchmdbmfLUXD0w/). This diff implements and enables the ```RedactedEventsManager```. Reviewed By: Nathaaaalie Differential Revision: D51214260 fbshipit-source-id: 7c4111be7aa26acf046b1227d0204f49ccc0fb9f --- .../FBSDKCoreKit/AppEvents/FBSDKAppEvents.m | 5 + .../Integrity/RedactedEventsManager.swift | 66 +++++- .../Configuration/CoreKitConfigurator.swift | 1 + .../AppEvents/AppEventsStateTests.swift | 7 +- .../Internal/AppEvents/AppEventsTests.swift | 24 +++ .../RedactedEventsManagerTests.swift | 194 ++++++++++++++++++ .../CoreKitConfiguratorTests.swift | 6 +- 7 files changed, 299 insertions(+), 4 deletions(-) create mode 100644 FBSDKCoreKit/FBSDKCoreKitTests/Internal/AppEvents/Integrity/RedactedEventsManagerTests.swift diff --git a/FBSDKCoreKit/FBSDKCoreKit/AppEvents/FBSDKAppEvents.m b/FBSDKCoreKit/FBSDKCoreKit/AppEvents/FBSDKAppEvents.m index 70b72c354..a40969864 100644 --- a/FBSDKCoreKit/FBSDKCoreKit/AppEvents/FBSDKAppEvents.m +++ b/FBSDKCoreKit/FBSDKCoreKit/AppEvents/FBSDKAppEvents.m @@ -972,6 +972,11 @@ - (void)fetchServerConfiguration:(FBSDKCodeBlock)callback [self.blocklistEventsManager enable]; } }]; + [self.featureChecker checkFeature:FBSDKFeatureFilterRedactedEvents completionBlock:^(BOOL enabled) { + if (enabled) { + [self.redactedEventsManager enable]; + } + }]; if (@available(iOS 14.0, *)) { __weak FBSDKAppEvents *weakSelf = self; [self.featureChecker checkFeature:FBSDKFeatureATELogging completionBlock:^(BOOL enabled) { diff --git a/FBSDKCoreKit/FBSDKCoreKit/AppEvents/Internal/Integrity/RedactedEventsManager.swift b/FBSDKCoreKit/FBSDKCoreKit/AppEvents/Internal/Integrity/RedactedEventsManager.swift index c5c1e37d3..f32fe503d 100644 --- a/FBSDKCoreKit/FBSDKCoreKit/AppEvents/Internal/Integrity/RedactedEventsManager.swift +++ b/FBSDKCoreKit/FBSDKCoreKit/AppEvents/Internal/Integrity/RedactedEventsManager.swift @@ -11,6 +11,12 @@ import Foundation @objc(FBSDKRedactedEventsManager) final class RedactedEventsManager: NSObject, _EventsProcessing { + private var isEnabled = false + private var redactedEventsConfig = [String: Set]() + private static let redactedEventsKey = "redacted_events" + private static let eventKey = "event" + private static let isImplicitKey = "isImplicit" + var configuredDependencies: ObjectDependencies? var defaultDependencies: ObjectDependencies? = .init( @@ -18,11 +24,67 @@ final class RedactedEventsManager: NSObject, _EventsProcessing { ) func enable() { - // TODO: Implement this + guard let dependencies = try? getDependencies() else { + return + } + guard let redactedEvents = dependencies.serverConfigurationProvider + .cachedServerConfiguration() + .protectedModeRules?[RedactedEventsManager.redactedEventsKey] as? [[String: Any]] + else { return } + redactedEventsConfig = getRedactedEventsConfig(redactedEvents: redactedEvents) + if !redactedEventsConfig.isEmpty { + isEnabled = true + } } func processEvents(_ events: NSMutableArray) { - // TODO: Implement this + guard isEnabled else { return } + + var filteredEvents = [Any]() + events.forEach { eventDictionary in + guard let eventDictionaryTyped = eventDictionary as? [String: Any], + var event = eventDictionaryTyped[RedactedEventsManager.eventKey] as? [NSString: Any], + let eventName = (event as [String: Any])[AppEvents.ParameterName.eventName.rawValue] as? String + else { return } + if let redactionString = getRedactionStringFor(eventName: eventName) { + event[AppEvents.ParameterName.eventName.rawValue as NSString] = redactionString + let redactedEvent = NSMutableDictionary() + redactedEvent[RedactedEventsManager.eventKey] = NSMutableDictionary(dictionary: event) + redactedEvent[RedactedEventsManager.isImplicitKey] = eventDictionaryTyped[RedactedEventsManager.isImplicitKey] + filteredEvents.append(redactedEvent) + } else { + filteredEvents.append(eventDictionary) + } + } + events.removeAllObjects() + events.addObjects(from: filteredEvents) + } + + private func getRedactedEventsConfig(redactedEvents: [[String: Any]]) -> [String: Set] { + var config = [String: Set]() + for redactedEventDict in redactedEvents { + if let key = redactedEventDict["key"] as? String, + let value = redactedEventDict["value"] as? [String] { + if !config.keys.contains(key) { + config[key] = Set(value) + } else { + config[key] = config[key]?.union(Set(value)) + } + } + } + return config + } + + private func getRedactionStringFor(eventName: String) -> String? { + guard isEnabled else { return nil } + + for redactionString in redactedEventsConfig.keys { + if let redactedEvents = redactedEventsConfig[redactionString], + redactedEvents.contains(eventName) { + return redactionString + } + } + return nil } } diff --git a/FBSDKCoreKit/FBSDKCoreKit/Internal/Configuration/CoreKitConfigurator.swift b/FBSDKCoreKit/FBSDKCoreKit/Internal/Configuration/CoreKitConfigurator.swift index 1283305d4..d9774a67e 100644 --- a/FBSDKCoreKit/FBSDKCoreKit/Internal/Configuration/CoreKitConfigurator.swift +++ b/FBSDKCoreKit/FBSDKCoreKit/Internal/Configuration/CoreKitConfigurator.swift @@ -132,6 +132,7 @@ private extension CoreKitConfigurator { components.eventDeactivationManager, components.blocklistEventsManager, components.restrictiveDataFilterManager, + components.redactedEventsManager, ] } diff --git a/FBSDKCoreKit/FBSDKCoreKitTests/Internal/AppEvents/AppEventsStateTests.swift b/FBSDKCoreKit/FBSDKCoreKitTests/Internal/AppEvents/AppEventsStateTests.swift index 4015115bf..af1cf7230 100644 --- a/FBSDKCoreKit/FBSDKCoreKitTests/Internal/AppEvents/AppEventsStateTests.swift +++ b/FBSDKCoreKit/FBSDKCoreKitTests/Internal/AppEvents/AppEventsStateTests.swift @@ -16,6 +16,7 @@ final class AppEventsStateTests: XCTestCase { let appId = "appid" let eventsProcessor = TestAppEventsParameterProcessor() let blocklistEventsManager = TestBlocklistEventsManager() + let redactedEventsManager = TestRedactedEventsManager() lazy var state = _AppEventsState(token: self.name, appID: appId) lazy var partiallyFullState = _AppEventsState( token: self.name, @@ -27,7 +28,7 @@ final class AppEventsStateTests: XCTestCase { super.setUp() setUpFixtures() - _AppEventsState.eventProcessors = [eventsProcessor, blocklistEventsManager] + _AppEventsState.eventProcessors = [eventsProcessor, blocklistEventsManager, redactedEventsManager] } func setUpFixtures( @@ -526,5 +527,9 @@ final class AppEventsStateTests: XCTestCase { blocklistEventsManager.processEventsWasCalled, "Blocklist events manager should process events" ) + XCTAssertTrue( + redactedEventsManager.processEventsWasCalled, + "Redacted events manager should process events" + ) } } diff --git a/FBSDKCoreKit/FBSDKCoreKitTests/Internal/AppEvents/AppEventsTests.swift b/FBSDKCoreKit/FBSDKCoreKitTests/Internal/AppEvents/AppEventsTests.swift index a100b3ddf..b8ce29a28 100644 --- a/FBSDKCoreKit/FBSDKCoreKitTests/Internal/AppEvents/AppEventsTests.swift +++ b/FBSDKCoreKit/FBSDKCoreKitTests/Internal/AppEvents/AppEventsTests.swift @@ -1404,6 +1404,30 @@ final class AppEventsTests: XCTestCase { ) } + func testEnablingRedactedEvents() { + appEvents.fetchServerConfiguration(nil) + appEventsConfigurationProvider.firstCapturedBlock?() + let configuration = TestServerConfiguration(appID: name) + + serverConfigurationProvider.capturedCompletionBlock?(configuration, nil) + featureManager.completeCheck(forFeature: .filterRedactedEvents, with: true) + + XCTAssertTrue( + redactedEventsManager.enabledWasCalled, + "Should enable blocklist events when the feature is enabled and the server configuration allows it" + ) + } + + func testFetchingConfigurationIncludingRedactedEvents() { + appEvents.fetchServerConfiguration(nil) + appEventsConfigurationProvider.firstCapturedBlock?() + serverConfigurationProvider.capturedCompletionBlock?(nil, nil) + XCTAssertTrue( + featureManager.capturedFeaturesContains(.filterRedactedEvents), + "Fetching a configuration should check if the BlocklistEvents feature is enabled" + ) + } + func testFetchingConfigurationIncludingEventDeactivation() { appEvents.fetchServerConfiguration(nil) appEventsConfigurationProvider.firstCapturedBlock?() diff --git a/FBSDKCoreKit/FBSDKCoreKitTests/Internal/AppEvents/Integrity/RedactedEventsManagerTests.swift b/FBSDKCoreKit/FBSDKCoreKitTests/Internal/AppEvents/Integrity/RedactedEventsManagerTests.swift new file mode 100644 index 000000000..bb8c09dc8 --- /dev/null +++ b/FBSDKCoreKit/FBSDKCoreKitTests/Internal/AppEvents/Integrity/RedactedEventsManagerTests.swift @@ -0,0 +1,194 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * This source code is licensed under the license found in the + * LICENSE file in the root directory of this source tree. + */ + +@testable import FBSDKCoreKit + +final class RedactedEventsManagerTests: XCTestCase { + + let serverConfigDict = [ + "protectedModeRules": [ + "redacted_events": [ + [ + "key": "FilteredEvent", + "value": ["test_filtered_event_1", "test_filtered_event_2"], + ], + [ + "key": "RedactedEvent", + "value": ["test_redacted_event_1", "test_redacted_event_2"], + ], + ], + ], + ] + + lazy var serverConfiguration = ServerConfigurationFixtures.configuration(withDictionary: serverConfigDict) + + // swiftlint:disable implicitly_unwrapped_optional + var provider: TestServerConfigurationProvider! + var redactedEventsManager: RedactedEventsManager! + // swiftlint:enable implicitly_unwrapped_optional + + override func setUp() { + super.setUp() + provider = TestServerConfigurationProvider(configuration: serverConfiguration) + redactedEventsManager = RedactedEventsManager() + redactedEventsManager.configuredDependencies = .init( + serverConfigurationProvider: provider + ) + } + + override func tearDown() { + super.tearDown() + redactedEventsManager = nil + provider = nil + } + + private func getTestEvents() -> NSMutableArray { + let event1 = [ + AppEvents.ParameterName.eventName: "test_filtered_event_1", + AppEvents.ParameterName.currency: "USD", + AppEvents.ParameterName.description: "This is a filtered event", + ] + let event2 = [ + AppEvents.ParameterName.eventName: "test_filtered_event_2", + AppEvents.ParameterName.currency: "EUR", + AppEvents.ParameterName.description: "This is a filtered event", + ] + let event3 = [ + AppEvents.ParameterName.eventName: "test_redacted_event_1", + AppEvents.ParameterName.currency: "FEN", + AppEvents.ParameterName.description: "This is a redacted event", + ] + let event4 = [ + AppEvents.ParameterName.eventName: "test_redacted_event_2", + AppEvents.ParameterName.currency: "GBP", + AppEvents.ParameterName.description: "This is a redacted event", + ] + let event5 = [ + AppEvents.ParameterName.eventName: "test_non_sensitive_event", + AppEvents.ParameterName.currency: "CAD", + AppEvents.ParameterName.description: "This is a non-sensitive event", + ] + let events = NSMutableArray() + events.add(["event": event1, "isImplicit": false]) + events.add(["event": event2, "isImplicit": false]) + events.add(["event": event3, "isImplicit": false]) + events.add(["event": event4, "isImplicit": false]) + events.add(["event": event5, "isImplicit": false]) + return events + } + + func testDefaultDependencies() throws { + redactedEventsManager.resetDependencies() + XCTAssertTrue( + redactedEventsManager.serverConfigurationProvider === _ServerConfigurationManager.shared, + "Should use the shared server configuration manger by default" + ) + } + + func testConfiguringDependencies() { + XCTAssertTrue( + redactedEventsManager.serverConfigurationProvider === provider, + "Should be able to create with a server configuration provider" + ) + } + + func testProcessEventsNotEnabled() { + let events = getTestEvents() + redactedEventsManager.processEvents(events) + XCTAssertTrue( + events.count == 5, + "redactedEventsManager should not drop any events" + ) + guard let eventDict0 = events[0] as? [String: Any], + let event0 = eventDict0["event"] as? [AppEvents.ParameterName: String], + let eventDict1 = events[1] as? [String: Any], + let event1 = eventDict1["event"] as? [AppEvents.ParameterName: String], + let eventDict2 = events[2] as? [String: Any], + let event2 = eventDict2["event"] as? [AppEvents.ParameterName: String], + let eventDict3 = events[3] as? [String: Any], + let event3 = eventDict3["event"] as? [AppEvents.ParameterName: String], + let eventDict4 = events[4] as? [String: Any], + let event4 = eventDict4["event"] as? [AppEvents.ParameterName: String] else { + XCTFail("events has incorrect structure") + return + } + XCTAssertEqual( + event0[AppEvents.ParameterName.eventName], + "test_filtered_event_1", + "Event at index 0 has an incorrect event name" + ) + XCTAssertEqual( + event1[AppEvents.ParameterName.eventName], + "test_filtered_event_2", + "Event at index 1 has an incorrect event name" + ) + XCTAssertEqual( + event2[AppEvents.ParameterName.eventName], + "test_redacted_event_1", + "Event at index 2 has an incorrect event name" + ) + XCTAssertEqual( + event3[AppEvents.ParameterName.eventName], + "test_redacted_event_2", + "Event at index 3 has an incorrect event name" + ) + XCTAssertEqual( + event4[AppEvents.ParameterName.eventName], + "test_non_sensitive_event", + "Event at index 4 has an incorrect event name" + ) + } + + func testProcessEventsEnabled() { + redactedEventsManager.enable() + let events = getTestEvents() + redactedEventsManager.processEvents(events) + XCTAssertTrue( + events.count == 5, + "redactedEventsManager should not drop any events" + ) + guard let eventDict0 = events[0] as? [String: Any], + let event0 = eventDict0["event"] as? [AppEvents.ParameterName: String], + let eventDict1 = events[1] as? [String: Any], + let event1 = eventDict1["event"] as? [AppEvents.ParameterName: String], + let eventDict2 = events[2] as? [String: Any], + let event2 = eventDict2["event"] as? [AppEvents.ParameterName: String], + let eventDict3 = events[3] as? [String: Any], + let event3 = eventDict3["event"] as? [AppEvents.ParameterName: String], + let eventDict4 = events[4] as? [String: Any], + let event4 = eventDict4["event"] as? [AppEvents.ParameterName: String] else { + XCTFail("events has incorrect structure") + return + } + XCTAssertEqual( + event0[AppEvents.ParameterName.eventName], + "FilteredEvent", + "Event at index 0 has an incorrect event name" + ) + XCTAssertEqual( + event1[AppEvents.ParameterName.eventName], + "FilteredEvent", + "Event at index 1 has an incorrect event name" + ) + XCTAssertEqual( + event2[AppEvents.ParameterName.eventName], + "RedactedEvent", + "Event at index 2 has an incorrect event name" + ) + XCTAssertEqual( + event3[AppEvents.ParameterName.eventName], + "RedactedEvent", + "Event at index 3 has an incorrect event name" + ) + XCTAssertEqual( + event4[AppEvents.ParameterName.eventName], + "test_non_sensitive_event", + "Event at index 4 has an incorrect event name" + ) + } +} diff --git a/FBSDKCoreKit/FBSDKCoreKitTests/Internal/Configuration/CoreKitConfiguratorTests.swift b/FBSDKCoreKit/FBSDKCoreKitTests/Internal/Configuration/CoreKitConfiguratorTests.swift index 5b7b22f93..ca2398a8c 100644 --- a/FBSDKCoreKit/FBSDKCoreKitTests/Internal/Configuration/CoreKitConfiguratorTests.swift +++ b/FBSDKCoreKit/FBSDKCoreKitTests/Internal/Configuration/CoreKitConfiguratorTests.swift @@ -394,7 +394,7 @@ final class CoreKitConfiguratorTests: XCTestCase { _AppEventsState.eventProcessors, "_AppEventsState's event processors should be configured" ) - XCTAssertEqual(processors.count, 3, "_AppEventsState should have three event processors") + XCTAssertEqual(processors.count, 4, "_AppEventsState should have three event processors") XCTAssertTrue( processors[0] === components.eventDeactivationManager, "_AppEventsState's event processors should be configured with the event deactivation manager" @@ -407,6 +407,10 @@ final class CoreKitConfiguratorTests: XCTestCase { processors[2] === components.restrictiveDataFilterManager, "_AppEventsState's event processors should be configured with the restrictive data filter manager" ) + XCTAssertTrue( + processors[3] === components.redactedEventsManager, + "_AppEventsState's event processors should be configured with the redacted events manager" + ) } func testConfiguringAppEventsUtility() {