Skip to content

Commit

Permalink
Allow manually tracked resources to detect first party hosts
Browse files Browse the repository at this point in the history
First party hosts set by `trackUrlSession` only works for automatically instrumented URLSessions. This allows manual calls to startResourceLoading and stopResourceLoading to detect first party host calls as well.
  • Loading branch information
fuzzybinary committed May 2, 2022
1 parent 8c1d293 commit afc8c1c
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Unreleased

* [BUGFIX] Fix rare problem with bringing up the "Local Network Permission" alert. See [#830][]
* [IMPROVEMENT] Allow manually tracked resources in RUM Sessions to detect first party hosts.

# 1.11.0-beta1 / 04-26-2022

Expand Down
19 changes: 13 additions & 6 deletions Sources/Datadog/Core/FeaturesConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ internal struct FeaturesConfiguration {
let instrumentation: Instrumentation?
let backgroundEventTrackingEnabled: Bool
let onSessionStart: RUMSessionListener?
let firstPartyHosts: Set<String>
}

struct URLSessionAutoInstrumentation {
Expand Down Expand Up @@ -184,6 +185,14 @@ extension FeaturesConfiguration {
)
}

var sanitizedHosts: Set<String> = []
if let firstPartyHosts = configuration.firstPartyHosts {
sanitizedHosts = hostsSanitizer.sanitized(
hosts: firstPartyHosts,
warningMessage: "The first party host configured for Datadog SDK is not valid"
)
}

if configuration.rumEnabled {
let instrumentation = RUM.Instrumentation(
uiKitRUMViewsPredicate: configuration.rumUIKitViewsPredicate,
Expand All @@ -207,7 +216,8 @@ extension FeaturesConfiguration {
longTaskEventMapper: configuration.rumLongTaskEventMapper,
instrumentation: instrumentation,
backgroundEventTrackingEnabled: configuration.rumBackgroundEventTrackingEnabled,
onSessionStart: configuration.rumSessionsListener
onSessionStart: configuration.rumSessionsListener,
firstPartyHosts: sanitizedHosts
)
} else {
let error = ProgrammerError(
Expand All @@ -220,13 +230,10 @@ extension FeaturesConfiguration {
}
}

if let firstPartyHosts = configuration.firstPartyHosts {
if configuration.firstPartyHosts != nil {
if configuration.tracingEnabled || configuration.rumEnabled {
urlSessionAutoInstrumentation = URLSessionAutoInstrumentation(
userDefinedFirstPartyHosts: hostsSanitizer.sanitized(
hosts: firstPartyHosts,
warningMessage: "The first party host configured for Datadog SDK is not valid"
),
userDefinedFirstPartyHosts: sanitizedHosts,
sdkInternalURLs: [
logsEndpoint.url,
tracesEndpoint.url,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ internal struct RUMScopeDependencies {
let applicationVersion: String
let sdkVersion: String
let source: String
let firstPartyURLsFilter: FirstPartyURLsFilter
let eventBuilder: RUMEventBuilder
let eventOutput: RUMEventOutput
let rumUUIDGenerator: RUMUUIDGenerator
Expand Down Expand Up @@ -61,6 +62,7 @@ internal extension RUMScopeDependencies {
applicationVersion: rumFeature.configuration.common.applicationVersion,
sdkVersion: rumFeature.configuration.common.sdkVersion,
source: rumFeature.configuration.common.source,
firstPartyURLsFilter: FirstPartyURLsFilter(hosts: rumFeature.configuration.firstPartyHosts),
eventBuilder: RUMEventBuilder(
eventsMapper: rumFeature.eventsMapper
),
Expand Down
6 changes: 3 additions & 3 deletions Sources/Datadog/RUMMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ public class RUMMonitor: DDRUMMonitor, RUMCommandSubscriber {
url: request.url?.absoluteString ?? "unknown_url",
httpMethod: RUMMethod(httpMethod: request.httpMethod),
kind: RUMResourceType(request: request),
isFirstPartyRequest: nil,
isFirstPartyRequest: applicationScope.dependencies.firstPartyURLsFilter.isFirstParty(url: request.url),
spanContext: nil
)
)
Expand All @@ -368,7 +368,7 @@ public class RUMMonitor: DDRUMMonitor, RUMCommandSubscriber {
url: url.absoluteString,
httpMethod: .get,
kind: nil,
isFirstPartyRequest: nil,
isFirstPartyRequest: applicationScope.dependencies.firstPartyURLsFilter.isFirstParty(url: url),
spanContext: nil
)
)
Expand All @@ -388,7 +388,7 @@ public class RUMMonitor: DDRUMMonitor, RUMCommandSubscriber {
url: urlString,
httpMethod: httpMethod,
kind: nil,
isFirstPartyRequest: nil,
isFirstPartyRequest: applicationScope.dependencies.firstPartyURLsFilter.isFirstParty(string: urlString),
spanContext: nil
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,15 @@ internal struct FirstPartyURLsFilter {
}
return host.range(of: regex, options: .regularExpression) != nil
}

// Returns `true` if given `String` can be parsed as a URL and matches the first
// party hosts defined by the user; `false` otherwise
func isFirstParty(string: String) -> Bool {
guard let url = URL(string: string),
let regex = self.regex,
let host = url.host else {
return false
}
return host.range(of: regex, options: .regularExpression) != nil
}
}
6 changes: 4 additions & 2 deletions Tests/DatadogTests/Datadog/Mocks/CoreMocks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,8 @@ extension FeaturesConfiguration.RUM {
longTaskEventMapper: RUMLongTaskEventMapper? = nil,
instrumentation: FeaturesConfiguration.RUM.Instrumentation? = nil,
backgroundEventTrackingEnabled: Bool = false,
onSessionStart: @escaping RUMSessionListener = mockNoOpSessionListener()
onSessionStart: @escaping RUMSessionListener = mockNoOpSessionListener(),
firstPartyHosts: Set<String> = []
) -> Self {
return .init(
common: common,
Expand All @@ -285,7 +286,8 @@ extension FeaturesConfiguration.RUM {
longTaskEventMapper: longTaskEventMapper,
instrumentation: instrumentation,
backgroundEventTrackingEnabled: backgroundEventTrackingEnabled,
onSessionStart: onSessionStart
onSessionStart: onSessionStart,
firstPartyHosts: firstPartyHosts
)
}
}
Expand Down
3 changes: 3 additions & 0 deletions Tests/DatadogTests/Datadog/Mocks/RUMFeatureMocks.swift
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,7 @@ extension RUMScopeDependencies {
applicationVersion: applicationVersion,
sdkVersion: sdkVersion,
source: source,
firstPartyURLsFilter: FirstPartyURLsFilter(hosts: []),
eventBuilder: eventBuilder,
eventOutput: eventOutput,
rumUUIDGenerator: rumUUIDGenerator,
Expand Down Expand Up @@ -714,6 +715,7 @@ extension RUMScopeDependencies {
applicationVersion: String? = nil,
sdkVersion: String? = nil,
source: String? = nil,
firstPartyUrls: Set<String>? = nil,
eventBuilder: RUMEventBuilder? = nil,
eventOutput: RUMEventOutput? = nil,
rumUUIDGenerator: RUMUUIDGenerator? = nil,
Expand All @@ -736,6 +738,7 @@ extension RUMScopeDependencies {
applicationVersion: applicationVersion ?? self.applicationVersion,
sdkVersion: sdkVersion ?? self.sdkVersion,
source: source ?? self.source,
firstPartyURLsFilter: firstPartyUrls != nil ? FirstPartyURLsFilter(hosts: firstPartyUrls!) : self.firstPartyURLsFilter,
eventBuilder: eventBuilder ?? self.eventBuilder,
eventOutput: eventOutput ?? self.eventOutput,
rumUUIDGenerator: rumUUIDGenerator ?? self.rumUUIDGenerator,
Expand Down
52 changes: 52 additions & 0 deletions Tests/DatadogTests/Datadog/RUMMonitorTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ class RUMMonitorTests: XCTestCase {
let resourceEvent = session.viewVisits[0].resourceEvents[0]
XCTAssertEqual(resourceEvent.resource.url, url.absoluteString)
XCTAssertEqual(resourceEvent.resource.statusCode, 200)
XCTAssertNil(resourceEvent.resource.provider?.type)
}

func testStartingView_thenLoadingResourceWithURLString() throws {
Expand All @@ -284,6 +285,57 @@ class RUMMonitorTests: XCTestCase {
XCTAssertEqual(resourceEvent.resource.statusCode, 333)
XCTAssertEqual(resourceEvent.resource.type, .beacon)
XCTAssertEqual(resourceEvent.resource.method, .post)
XCTAssertNil(resourceEvent.resource.provider?.type)
}

func testLoadingResourceWithURL_thenMarksFirstPartyURLs() throws {
RUMFeature.instance = .mockByRecordingRUMEventMatchers(
directories: temporaryFeatureDirectories,
configuration: .mockWith(
// .mockRandom always uses foo.com
firstPartyHosts: ["foo.com"]
)
)
defer { RUMFeature.instance?.deinitialize() }

let monitor = try createTestableRUMMonitor()
setGlobalAttributes(of: monitor)

let url: URL = .mockRandom()
monitor.startView(viewController: mockView)
monitor.startResourceLoading(resourceKey: "/resource/1", url: url)
monitor.stopResourceLoading(resourceKey: "/resource/1", response: .mockWith(statusCode: 200, mimeType: "image/png"))

let rumEventMatchers = try RUMFeature.waitAndReturnRUMEventMatchers(count: 4)
verifyGlobalAttributes(in: rumEventMatchers)

let session = try XCTUnwrap(try RUMSessionMatcher.groupMatchersBySessions(rumEventMatchers).first)
let resourceEvent = session.viewVisits[0].resourceEvents[0]
XCTAssertEqual(resourceEvent.resource.provider?.type, RUMResourceEvent.Resource.Provider.ProviderType.firstParty)
}

func testLoadingResourceWithURLString_thenMarksFirstPartyURLs() throws {
RUMFeature.instance = .mockByRecordingRUMEventMatchers(
directories: temporaryFeatureDirectories,
configuration: .mockWith(
firstPartyHosts: ["foo.com"]
)
)
defer { RUMFeature.instance?.deinitialize() }

let monitor = try createTestableRUMMonitor()
setGlobalAttributes(of: monitor)

monitor.startView(viewController: mockView)
monitor.startResourceLoading(resourceKey: "/resource/1", httpMethod: .post, urlString: "http://www.foo.com/some/url/string", attributes: [:])
monitor.stopResourceLoading(resourceKey: "/resource/1", statusCode: 333, kind: .beacon)

let rumEventMatchers = try RUMFeature.waitAndReturnRUMEventMatchers(count: 4)
verifyGlobalAttributes(in: rumEventMatchers)

let session = try XCTUnwrap(try RUMSessionMatcher.groupMatchersBySessions(rumEventMatchers).first)
let resourceEvent = session.viewVisits[0].resourceEvents[0]
XCTAssertEqual(resourceEvent.resource.provider?.type, RUMResourceEvent.Resource.Provider.ProviderType.firstParty)
}

func testStartingView_thenTappingButton() throws {
Expand Down

0 comments on commit afc8c1c

Please sign in to comment.