Skip to content

Commit

Permalink
Implement and Enable Redacted Events Manager
Browse files Browse the repository at this point in the history
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
  • Loading branch information
ryantobinmeta authored and facebook-github-bot committed Dec 14, 2023
1 parent 75e965e commit 066eb63
Show file tree
Hide file tree
Showing 7 changed files with 299 additions and 4 deletions.
5 changes: 5 additions & 0 deletions FBSDKCoreKit/FBSDKCoreKit/AppEvents/FBSDKAppEvents.m
Expand Up @@ -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) {
Expand Down
Expand Up @@ -11,18 +11,80 @@ import Foundation
@objc(FBSDKRedactedEventsManager)
final class RedactedEventsManager: NSObject, _EventsProcessing {

private var isEnabled = false
private var redactedEventsConfig = [String: Set<String>]()
private static let redactedEventsKey = "redacted_events"
private static let eventKey = "event"
private static let isImplicitKey = "isImplicit"

var configuredDependencies: ObjectDependencies?

var defaultDependencies: ObjectDependencies? = .init(
serverConfigurationProvider: _ServerConfigurationManager.shared
)

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<String>] {
var config = [String: Set<String>]()
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
}
}

Expand Down
Expand Up @@ -132,6 +132,7 @@ private extension CoreKitConfigurator {
components.eventDeactivationManager,
components.blocklistEventsManager,
components.restrictiveDataFilterManager,
components.redactedEventsManager,
]
}

Expand Down
Expand Up @@ -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,
Expand All @@ -27,7 +28,7 @@ final class AppEventsStateTests: XCTestCase {
super.setUp()

setUpFixtures()
_AppEventsState.eventProcessors = [eventsProcessor, blocklistEventsManager]
_AppEventsState.eventProcessors = [eventsProcessor, blocklistEventsManager, redactedEventsManager]
}

func setUpFixtures(
Expand Down Expand Up @@ -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"
)
}
}
Expand Up @@ -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?()
Expand Down
@@ -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"
)
}
}
Expand Up @@ -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"
Expand All @@ -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() {
Expand Down

0 comments on commit 066eb63

Please sign in to comment.