Skip to content

Commit

Permalink
Support privacy config v2 (#84)
Browse files Browse the repository at this point in the history
* Support privacy config v2

* Make extension public

* Better version comparison

Co-authored-by: Sam Symons <[email protected]>
  • Loading branch information
SlayterDev and samsymons authored Apr 22, 2022
1 parent 657039e commit bd9b421
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 4 deletions.
31 changes: 31 additions & 0 deletions Sources/BrowserServicesKit/Common/Extensions/BundleExtension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//
// BundleExtension.swift
// DuckDuckGo
//
// Copyright © 2021 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation

extension Bundle {

struct Keys {
static let versionNumber = "CFBundleShortVersionString"
}

var releaseVersionNumber: String? {
return infoDictionary?[Keys.versionNumber] as? String
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,39 @@ public struct AppPrivacyConfiguration: PrivacyConfiguration {
return data.trackerAllowlist.state == PrivacyConfigurationData.State.enabled ? data.trackerAllowlist.entries : [:]
}

public func isEnabled(featureKey: PrivacyFeature) -> Bool {
func parse(versionString: String) -> [Int] {
return versionString.split(separator: ".").map { Int($0) ?? 0 }
}

func satisfiesMinVersion(feature: PrivacyConfigurationData.PrivacyFeature,
versionProvider: AppVersionProvider) -> Bool {
if let minSupportedVersion = feature.minSupportedVersion,
let appVersion = versionProvider.appVersion() {
let minVersion = parse(versionString: minSupportedVersion)
let currentVersion = parse(versionString: appVersion)

for i in 0..<max(minVersion.count, currentVersion.count) {
let minSegment = i < minVersion.count ? minVersion[i] : 0
let currSegment = i < currentVersion.count ? currentVersion[i] : 0

if currSegment > minSegment {
return true
}
if currSegment < minSegment {
return false
}
}
}

return true
}

public func isEnabled(featureKey: PrivacyFeature,
versionProvider: AppVersionProvider = AppVersionProvider()) -> Bool {
guard let feature = data.features[featureKey.rawValue] else { return false }

return feature.state == PrivacyConfigurationData.State.enabled
return satisfiesMinVersion(feature: feature, versionProvider: versionProvider)
&& feature.state == PrivacyConfigurationData.State.enabled
}

public func exceptionsList(forFeature featureKey: PrivacyFeature) -> [String] {
Expand Down
30 changes: 30 additions & 0 deletions Sources/BrowserServicesKit/PrivacyConfig/AppVersionProvider.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// AppVersionProvider.swift
// DuckDuckGo
//
// Copyright © 2021 DuckDuckGo. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import Foundation

open class AppVersionProvider {
open func appVersion() -> String? {
return Bundle.main.releaseVersionNumber
}

public init() {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public protocol PrivacyConfiguration {
/// Trackers that has been allow listed because of site breakage
var trackerAllowlist: PrivacyConfigurationData.TrackerAllowlistData { get }

func isEnabled(featureKey: PrivacyFeature) -> Bool
func isEnabled(featureKey: PrivacyFeature, versionProvider: AppVersionProvider) -> Bool

/// Domains for which given PrivacyFeature is disabled.
///
Expand Down Expand Up @@ -84,6 +84,12 @@ public protocol PrivacyConfiguration {
func userDisabledProtection(forDomain: String)
}

public extension PrivacyConfiguration {
func isEnabled(featureKey: PrivacyFeature) -> Bool {
return isEnabled(featureKey: featureKey, versionProvider: AppVersionProvider())
}
}

public enum PrivacyFeature: String {
case contentBlocking
case fingerprintingTemporaryStorage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,16 +88,19 @@ public struct PrivacyConfigurationData {
public typealias FeatureState = String
public typealias ExceptionList = [ExceptionEntry]
public typealias FeatureSettings = [String: Any]
public typealias FeatureSupportedVersion = String

enum CodingKeys: String {
case state
case exceptions
case settings
case minSupportedVersion
}

public let state: FeatureState
public let exceptions: ExceptionList
public let settings: FeatureSettings
public let minSupportedVersion: FeatureSupportedVersion?

public init?(json: [String: Any]) {
guard let state = json[CodingKeys.state.rawValue] as? String else { return nil }
Expand All @@ -110,12 +113,14 @@ public struct PrivacyConfigurationData {
}

self.settings = (json[CodingKeys.settings.rawValue] as? [String: Any]) ?? [:]
self.minSupportedVersion = json[CodingKeys.minSupportedVersion.rawValue] as? String
}

public init(state: String, exceptions: [ExceptionEntry], settings: [String: Any] = [:]) {
public init(state: String, exceptions: [ExceptionEntry], settings: [String: Any] = [:], minSupportedVersion: String? = nil) {
self.state = state
self.exceptions = exceptions
self.settings = settings
self.minSupportedVersion = minSupportedVersion
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ class MockEmbeddedDataProvider: EmbeddedDataProvider {
}
}

class MockAppVersionProvider: AppVersionProvider {
var mockedVersion: String

override func appVersion() -> String {
return mockedVersion
}

init(appVersion: String) {
self.mockedVersion = appVersion
}
}

class AppPrivacyConfigurationTests: XCTestCase {

let embeddedConfig =
Expand Down Expand Up @@ -261,4 +273,70 @@ class AppPrivacyConfigurationTests: XCTestCase {
XCTAssertFalse(config.isFeature(.gpc, enabledForDomain: "example.com"))
XCTAssertFalse(config.isFeature(.gpc, enabledForDomain: "unp.com"))
}

let exampleVersionConfig =
"""
{
"features": {
"gpc": {
"state": "enabled",
"exceptions": [
{
"domain": "example.com",
"reason": "site breakage"
}
]
},
"trackingParameters": {
"state": "enabled",
"minSupportedVersion": "0.22.2",
"exceptions": []
},
"ampLinks": {
"state": "enabled",
"minSupportedVersion": "7.66.1.0",
"exceptions": []
}
},
"unprotectedTemporary": [
{
"domain": "unp.com",
"reason": "site breakage"
}
]
}
""".data(using: .utf8)!

func testMinSupportedVersionCheckReturnsCorrectly() {
var appVersion = MockAppVersionProvider(appVersion: "0.22.2")

let mockEmbeddedData = MockEmbeddedDataProvider(data: exampleVersionConfig, etag: "test")
let mockProtectionStore = MockDomainsProtectionStore()

let manager = PrivacyConfigurationManager(fetchedETag: nil,
fetchedData: nil,
embeddedDataProvider: mockEmbeddedData,
localProtection: mockProtectionStore)

let config = manager.privacyConfig

XCTAssertTrue(config.isEnabled(featureKey: .gpc, versionProvider: appVersion))
XCTAssertTrue(config.isEnabled(featureKey: .trackingParameters, versionProvider: appVersion))

appVersion = MockAppVersionProvider(appVersion: "0.22.3")
XCTAssertTrue(config.isEnabled(featureKey: .trackingParameters, versionProvider: appVersion))
appVersion = MockAppVersionProvider(appVersion: "1.0.0")
XCTAssertTrue(config.isEnabled(featureKey: .trackingParameters, versionProvider: appVersion))
appVersion = MockAppVersionProvider(appVersion: "1.0.0.0")
XCTAssertTrue(config.isEnabled(featureKey: .trackingParameters, versionProvider: appVersion))

// Test invalid version format
XCTAssertFalse(config.isEnabled(featureKey: .ampLinks, versionProvider: appVersion))

// Test unsupported version
appVersion = MockAppVersionProvider(appVersion: "0.22.0")
XCTAssertFalse(config.isEnabled(featureKey: .trackingParameters, versionProvider: appVersion))
appVersion = MockAppVersionProvider(appVersion: "7.65.1.0")
XCTAssertFalse(config.isEnabled(featureKey: .ampLinks, versionProvider: appVersion))
}
}

0 comments on commit bd9b421

Please sign in to comment.