Skip to content

Commit ac230ce

Browse files
kriedvolkdmitri
authored andcommitted
Fix possible duplication of tokens in route request (#10697)
1 parent 9cac80f commit ac230ce

File tree

10 files changed

+200
-5
lines changed

10 files changed

+200
-5
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changes to the Mapbox Navigation SDK for iOS
22

3+
## Unreleased
4+
5+
### Routing
6+
7+
* Fixed an issue where authentication route request parameters could be duplicated when using a custom `DirectionsOptions` subclass.
8+
39
## 3.16.0-rc.1
410

511
### Packaging

Sources/MapboxDirections/Directions.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ open class Directions: @unchecked Sendable {
644644
) -> URL {
645645
let includesQuery = httpMethod != "POST"
646646
var params = (includesQuery ? options.urlQueryItems : [])
647-
params.append(contentsOf: credentials.authenticationParams)
647+
params.override(with: credentials.authenticationParams)
648648

649649
let unparameterizedURL = URL(path: includesQuery ? options.path : options.abridgedPath, host: credentials.host)
650650
var components = URLComponents(url: unparameterizedURL, resolvingAgainstBaseURL: true)!

Sources/MapboxDirections/Extensions/Array.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,11 @@ extension Collection {
66
return try IndexSet(enumerated().filter { try predicate($0.element) }.map(\.offset))
77
}
88
}
9+
10+
extension [URLQueryItem] {
11+
mutating func override(with params: [URLQueryItem]) {
12+
let names = Set(params.map(\.name))
13+
removeAll { names.contains($0.name) }
14+
append(contentsOf: params)
15+
}
16+
}

Sources/MapboxDirections/Isochrones.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ open class Isochrones: @unchecked Sendable {
144144
/// - Returns: The URL to send the request to.
145145
open func url(forCalculating options: IsochroneOptions) -> URL {
146146
var params = options.urlQueryItems
147-
params.append(URLQueryItem(name: "access_token", value: credentials.accessToken))
147+
params.override(with: [URLQueryItem(name: "access_token", value: credentials.accessToken)])
148148

149149
let unparameterizedURL = URL(path: options.path, host: credentials.host)
150150
var components = URLComponents(url: unparameterizedURL, resolvingAgainstBaseURL: true)!

Sources/MapboxDirections/Matrix.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ open class Matrix: @unchecked Sendable {
145145
/// - Returns: The URL to send the request to.
146146
open func url(forCalculating options: MatrixOptions) -> URL {
147147
var params = options.urlQueryItems
148-
params.append(URLQueryItem(name: "access_token", value: credentials.accessToken))
148+
params.override(with: [URLQueryItem(name: "access_token", value: credentials.accessToken)])
149149

150150
let unparameterizedURL = URL(path: options.path, host: credentials.host)
151151
var components = URLComponents(url: unparameterizedURL, resolvingAgainstBaseURL: true)!

Tests/MapboxDirectionsTests/DirectionsTests.swift

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import Turf
1313

1414
let BogusToken = "pk.feedCafeDadeDeadBeef-BadeBede.FadeCafeDadeDeed-BadeBede"
1515
let BogusCredentials = Credentials(accessToken: BogusToken)
16+
private let accessTokenKey = "access_token"
17+
private let skuKey = "sku"
1618
let BadResponse = """
1719
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
1820
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
@@ -142,7 +144,6 @@ class DirectionsTests: XCTestCase {
142144
let directions = Directions(credentials: BogusCredentials)
143145
let opts = RouteOptions(locations: [one, two])
144146
directions.calculate(opts, completionHandler: { result in
145-
146147
guard case .failure(let error) = result else {
147148
XCTFail("Expecting error, none returned.")
148149
return
@@ -264,5 +265,78 @@ class DirectionsTests: XCTestCase {
264265
XCTAssertTrue(queryItems.contains(where: { $0.name == "sku" && $0.value == skuToken }))
265266
XCTAssertTrue(queryItems.contains(where: { $0.name == "access_token" && $0.value == BogusToken }))
266267
}
268+
269+
func testUrlWithCustomParametersWithAccessToken() {
270+
let customParameterKey = "custom_parameter"
271+
let customParameterValue = "custom_value"
272+
273+
let queryItems = requestQueryItems(with: [
274+
URLQueryItem(name: accessTokenKey, value: "invalid"),
275+
URLQueryItem(name: customParameterKey, value: customParameterValue),
276+
])
277+
278+
let accessTokenItems = queryItems.filter { $0.name == accessTokenKey }
279+
XCTAssertEqual(accessTokenItems.count, 1)
280+
let skuItems = queryItems.filter { $0.name == skuKey }
281+
XCTAssertEqual(skuItems.count, 1)
282+
XCTAssertTrue(queryItems.contains(where: { $0.name == skuKey && $0.value == skuToken }))
283+
XCTAssert(queryItems.contains(where: { $0.name == accessTokenKey && $0.value == BogusToken }))
284+
285+
XCTAssert(queryItems.contains(where: { $0.name == customParameterKey && $0.value == customParameterValue }))
286+
}
287+
288+
func testUrlWithCustomParametersWithSkuToken() {
289+
let skuKey = "sku"
290+
let customParameterKey = "custom_parameter"
291+
let customParameterValue = "custom_value"
292+
293+
let queryItems = requestQueryItems(with: [
294+
URLQueryItem(name: skuKey, value: "invalid"),
295+
URLQueryItem(name: customParameterKey, value: customParameterValue),
296+
])
297+
298+
let accessTokenItems = queryItems.filter { $0.name == accessTokenKey }
299+
XCTAssertEqual(accessTokenItems.count, 1)
300+
let skuItems = queryItems.filter { $0.name == skuKey }
301+
XCTAssertEqual(skuItems.count, 1)
302+
XCTAssertTrue(queryItems.contains(where: { $0.name == skuKey && $0.value == skuToken }))
303+
XCTAssert(queryItems.contains(where: { $0.name == accessTokenKey && $0.value == BogusToken }))
304+
305+
XCTAssert(queryItems.contains(where: { $0.name == customParameterKey && $0.value == customParameterValue }))
306+
}
307+
308+
func testUrlWithCustomParametersWithSkuAndAccessToken() {
309+
let customParameterKey = "custom_parameter"
310+
let customParameterValue = "custom_value"
311+
let queryItems = requestQueryItems(with: [
312+
URLQueryItem(name: accessTokenKey, value: "invalid_token"),
313+
URLQueryItem(name: skuKey, value: "invalid_sku"),
314+
URLQueryItem(name: customParameterKey, value: customParameterValue),
315+
])
316+
let accessTokenItems = queryItems.filter { $0.name == accessTokenKey }
317+
XCTAssertEqual(accessTokenItems.count, 1)
318+
let skuItems = queryItems.filter { $0.name == skuKey }
319+
XCTAssertEqual(skuItems.count, 1)
320+
XCTAssertTrue(queryItems.contains(where: { $0.name == skuKey && $0.value == skuToken }))
321+
XCTAssert(queryItems.contains(where: { $0.name == accessTokenKey && $0.value == BogusToken }))
322+
323+
XCTAssert(queryItems.contains(where: { $0.name == customParameterKey && $0.value == customParameterValue }))
324+
}
325+
326+
private func requestQueryItems(with customParameters: [URLQueryItem]) -> [URLQueryItem] {
327+
let coordinate = LocationCoordinate2D(latitude: 0, longitude: 0)
328+
let options = CustomRouteOptions(waypoints: [
329+
Waypoint(coordinate: coordinate),
330+
Waypoint(coordinate: coordinate),
331+
], customParameters: customParameters)
332+
333+
let directions = Directions(credentials: BogusCredentials)
334+
let url = directions.url(forCalculating: options, httpMethod: "GET")
335+
guard let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems else {
336+
XCTFail("Unexpected nil queryItems")
337+
return []
338+
}
339+
return queryItems
340+
}
267341
}
268342
#endif

Tests/MapboxDirectionsTests/MatrixTests.swift

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,38 @@ class MatrixTests: XCTestCase {
272272
})
273273
wait(for: [expectation], timeout: 2.0)
274274
}
275+
276+
func testUrlWithCustomParametersWithAccessToken() {
277+
let customParameterKey = "custom_parameter"
278+
let customParameterValue = "custom_value"
279+
let queryItems = requestQueryItems(with: [
280+
URLQueryItem(name: "access_token", value: "invalid_token"),
281+
URLQueryItem(name: customParameterKey, value: customParameterValue),
282+
])
283+
let accessTokenItems = queryItems.filter { $0.name == "access_token" }
284+
XCTAssertEqual(accessTokenItems.count, 1)
285+
XCTAssert(queryItems.contains(where: { $0.name == "access_token" && $0.value == BogusToken }))
286+
XCTAssert(queryItems.contains(where: { $0.name == customParameterKey && $0.value == customParameterValue }))
287+
}
288+
289+
private func requestQueryItems(with customParameters: [URLQueryItem]) -> [URLQueryItem] {
290+
let coordinate = LocationCoordinate2D(latitude: 0, longitude: 0)
291+
let waypoint = Waypoint(coordinate: coordinate)
292+
let options = CustomMatrixOptions(
293+
sources: [waypoint],
294+
destinations: [waypoint],
295+
profileIdentifier: .automobileAvoidingTraffic,
296+
customParameters: customParameters
297+
)
298+
299+
let matrix = Matrix(credentials: MatrixBogusCredentials)
300+
let url = matrix.url(forCalculating: options)
301+
guard let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems else {
302+
XCTFail("Unexpected nil queryItems")
303+
return []
304+
}
305+
return queryItems
306+
}
275307
}
276308
#endif
277309

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#if !os(Linux)
2+
@testable import MapboxDirections
3+
#if canImport(CoreLocation)
4+
import CoreLocation
5+
#endif
6+
7+
final class CustomMatrixOptions: MatrixOptions {
8+
var customParameters: [URLQueryItem]
9+
10+
init(
11+
sources: [Waypoint],
12+
destinations: [Waypoint],
13+
profileIdentifier: ProfileIdentifier,
14+
customParameters: [URLQueryItem] = []
15+
) {
16+
self.customParameters = customParameters
17+
18+
super.init(sources: sources, destinations: destinations, profileIdentifier: profileIdentifier)
19+
}
20+
21+
override var urlQueryItems: [URLQueryItem] {
22+
var combined = super.urlQueryItems
23+
combined.append(contentsOf: customParameters)
24+
return combined
25+
}
26+
27+
required init(from decoder: any Decoder) throws {
28+
self.customParameters = []
29+
try super.init(from: decoder)
30+
}
31+
}
32+
#endif
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#if !os(Linux)
2+
import MapboxDirections
3+
#if canImport(CoreLocation)
4+
import CoreLocation
5+
#endif
6+
7+
final class CustomRouteOptions: RouteOptions {
8+
var customParameters: [URLQueryItem]
9+
10+
init(
11+
waypoints: [Waypoint],
12+
profileIdentifier: ProfileIdentifier? = nil,
13+
customParameters: [URLQueryItem] = []
14+
) {
15+
self.customParameters = customParameters
16+
17+
super.init(waypoints: waypoints, profileIdentifier: profileIdentifier)
18+
}
19+
20+
required init(
21+
waypoints: [Waypoint],
22+
profileIdentifier: ProfileIdentifier? = nil,
23+
queryItems: [URLQueryItem]? = nil
24+
) {
25+
self.customParameters = []
26+
super.init(
27+
waypoints: waypoints,
28+
profileIdentifier: profileIdentifier,
29+
queryItems: queryItems
30+
)
31+
}
32+
33+
required init(from decoder: any Decoder) throws {
34+
self.customParameters = []
35+
try super.init(from: decoder)
36+
}
37+
38+
override var urlQueryItems: [URLQueryItem] {
39+
var combined = super.urlQueryItems
40+
combined.append(contentsOf: customParameters)
41+
return combined
42+
}
43+
}
44+
#endif

Tests/MapboxDirectionsTests/RouteRefreshTests.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@ class RouteRefreshTests: XCTestCase {
106106
let refreshResponseExpectation = expectation(description: "Refresh response with incorrect parameters failed.")
107107

108108
fetchStubbedRoute { routeResponse in
109-
110109
Directions(credentials: BogusCredentials).refreshRoute(
111110
responseIdentifier: routeResponse.identifier!,
112111
routeIndex: 10,

0 commit comments

Comments
 (0)