Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DIA-3558 expand usnat consent object #555

Merged
merged 6 commits into from
Mar 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions ConsentViewController/Classes/Consents/SPConsentable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// SPConsentable.swift
// Pods
//
// Created by Andre Herculano on 29.02.24.
//

import Foundation

protocol Consentable {
var id: String { get }
var consented: Bool { get }
}

@objcMembers public class SPConsentable: NSObject, Consentable, Codable {
enum CodingKeys: String, CodingKey {
case id = "_id"
case consented
}

public let id: String
public let consented: Bool

override open var description: String {
"SPConsentable(id: \(id), consented: \(consented))"
}

init(id: String, consented: Bool) {
self.id = id
self.consented = consented
}

override public func isEqual(_ object: Any?) -> Bool {
guard let other = object as? SPConsentable else { return false }

return other.id == id && other.consented == consented
}
}
179 changes: 147 additions & 32 deletions ConsentViewController/Classes/Consents/SPUSNatConsent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,29 @@
import Foundation

@objcMembers public class SPUSNatConsent: NSObject, Codable, CampaignConsent, NSCopying {
public struct ConsentString: Codable, Equatable {
let sectionId: Int
let sectionName, consentString: String
}
/// A collection of accepted/rejected vendors and their ids
public var vendors: [SPConsentable] { userConsents.vendors }

/// A collection of accepted/rejected categories (aka. purposes) and their ids
public var categories: [SPConsentable] { userConsents.categories }

/// Identifies this usnat consent profile
public var uuid: String?

/// Whether USNat applies according to user's location (inferred from IP lookup) and your Vendor List applies scope setting
public var applies: Bool

/// The consent strings related to this user profile
public let consentStrings: [ConsentString]

/// A dictionary with all GPP related data
public var GPPData: SPJson?
/// A series of statuses (`Bool?`) regarding GPP and user consent
/// - SeeAlso: `SPUSNatConsent.Statuses`
public var statuses: Statuses { .init(from: consentStatus) }

let categories: [String]
/// A dictionary with all GPP related data. Only available on Swift implementations.
/// ObjC projects will have to access this data via `UserDefaults` according to the GPP spec:
/// - SeeAlso: https://github.com/InteractiveAdvertisingBureau/Global-Privacy-Platform/blob/main/Core/CMP%20API%20Specification.md#in-app-details
public var GPPData: SPJson?

var dateCreated, expirationDate: SPDate

Expand All @@ -32,20 +40,12 @@ import Foundation
/// Used by the rendering app
let webConsentPayload: SPWebConsentPayload?

/// Used by SP endpoints and to derive the data inside `statuses`
var consentStatus: ConsentStatus

override open var description: String {
"""
SPUSNatConsent(
- uuid: \(uuid ?? "")
- applies: \(applies)
- consentStrings: \(consentStrings)
- categories: \(categories)
- dateCreated: \(dateCreated)
- expirationDate: \(expirationDate)
)
"""
}
/// Only here to make it easier encoding/decoding data from SP endpoints.
/// Used to derive the data in `vendors` and `categories`
var userConsents: UserConsents

init(
uuid: String? = nil,
Expand All @@ -55,7 +55,8 @@ import Foundation
consentStrings: [ConsentString],
webConsentPayload: SPWebConsentPayload? = nil,
lastMessage: LastMessageData? = nil,
categories: [String],
categories: [SPConsentable],
vendors: [SPConsentable],
consentStatus: ConsentStatus,
GPPData: SPJson? = nil
) {
Expand All @@ -66,9 +67,9 @@ import Foundation
self.consentStrings = consentStrings
self.webConsentPayload = webConsentPayload
self.lastMessage = lastMessage
self.categories = []
self.consentStatus = consentStatus
self.GPPData = GPPData
self.userConsents = UserConsents(vendors: vendors, categories: categories)
}

required public init(from decoder: Decoder) throws {
Expand All @@ -80,9 +81,25 @@ import Foundation
consentStrings = try container.decode([ConsentString].self, forKey: .consentStrings)
webConsentPayload = try container.decodeIfPresent(SPWebConsentPayload.self, forKey: .webConsentPayload)
lastMessage = try container.decodeIfPresent(LastMessageData.self, forKey: .lastMessage)
categories = try container.decode([String].self, forKey: .categories)
consentStatus = try container.decode(ConsentStatus.self, forKey: .consentStatus)
GPPData = try container.decodeIfPresent(SPJson.self, forKey: .GPPData)
userConsents = try container.decodeIfPresent(UserConsents.self, forKey: .userConsents) ?? UserConsents(vendors: [], categories: [])
}
}

extension SPUSNatConsent {
override open var description: String {
"""
SPUSNatConsent(
- uuid: \(uuid ?? "")
- applies: \(applies)
- consentStrings: \(consentStrings)
- categories: \(categories)
- vendors: \(vendors)
- dateCreated: \(dateCreated)
- expirationDate: \(expirationDate)
)
"""
}

public static func empty() -> SPUSNatConsent { SPUSNatConsent(
Expand All @@ -91,20 +108,20 @@ import Foundation
expirationDate: .distantFuture(),
consentStrings: [],
categories: [],
vendors: [],
consentStatus: ConsentStatus()
)}

override public func isEqual(_ object: Any?) -> Bool {
if let other = object as? SPUSNatConsent {
return other.uuid == uuid &&
other.applies == applies &&
other.consentStrings.count == consentStrings.count &&
other.consentStrings.sorted(by: { $0.sectionId > $1.sectionId }) ==
other.consentStrings.sorted(by: { $0.sectionId > $1.sectionId }) &&
other.categories == categories
} else {
return false
}
guard let other = object as? SPUSNatConsent else { return false }

return other.uuid == uuid &&
other.applies == applies &&
other.consentStrings.count == consentStrings.count &&
other.consentStrings.sorted(by: { $0.sectionId > $1.sectionId }) ==
other.consentStrings.sorted(by: { $0.sectionId > $1.sectionId }) &&
other.categories == categories &&
other.vendors == vendors
}

public func copy(with zone: NSZone? = nil) -> Any { SPUSNatConsent(
Expand All @@ -116,7 +133,105 @@ import Foundation
webConsentPayload: webConsentPayload,
lastMessage: lastMessage,
categories: categories,
vendors: vendors,
consentStatus: consentStatus,
GPPData: GPPData
)}
}

extension SPUSNatConsent {
struct UserConsents: Codable, Equatable {
let vendors, categories: [SPConsentable]
}
}

extension SPUSNatConsent {
@objcMembers public class ConsentString: NSObject, Codable {
public let sectionId: Int
public let sectionName, consentString: String

override open var description: String {
"""
SPUSNatConsent.ConsentString(
- sectionId: \(sectionId)
- sectionName: \(sectionName)
- consentString: \(consentString)
)
"""
}

init(sectionId: Int, sectionName: String, consentString: String) {
self.sectionId = sectionId
self.sectionName = sectionName
self.consentString = consentString
}

override public func isEqual(_ object: Any?) -> Bool {
guard let other = object as? ConsentString else { return false }

return other.sectionId == sectionId &&
other.sectionName == sectionName &&
other.consentString == consentString
}
}
}

extension SPUSNatConsent {
@objcMembers public class Statuses: NSObject {
let rejectedAny, consentedToAll, consentedToAny,
hasConsentData, sellStatus, shareStatus,
sensitiveDataStatus, gpcStatus: Bool?

override open var description: String {
"""
SPUSNatConsent.Statuses(
- rejectedAny: \(rejectedAny as Any)
- consentedToAll: \(consentedToAll as Any)
- consentedToAny: \(consentedToAny as Any)
- hasConsentData: \(hasConsentData as Any)
- sellStatus: \(sellStatus as Any)
- shareStatus: \(shareStatus as Any)
- sensitiveDataStatus: \(sensitiveDataStatus as Any)
- gpcStatus: \(gpcStatus as Any)
)
"""
}

init(from status: ConsentStatus) {
rejectedAny = status.rejectedAny
consentedToAll = status.consentedToAll
consentedToAny = status.consentedToAny
hasConsentData = status.hasConsentData
sellStatus = status.granularStatus?.sellStatus
shareStatus = status.granularStatus?.shareStatus
sensitiveDataStatus = status.granularStatus?.sensitiveDataStatus
gpcStatus = status.granularStatus?.gpcStatus
}

init(rejectedAny: Bool?, consentedToAll: Bool?, consentedToAny: Bool?,
hasConsentData: Bool?, sellStatus: Bool?, shareStatus: Bool?,
sensitiveDataStatus: Bool?, gpcStatus: Bool?) {
self.rejectedAny = rejectedAny
self.consentedToAll = consentedToAll
self.consentedToAny = consentedToAny
self.hasConsentData = hasConsentData
self.sellStatus = sellStatus
self.shareStatus = shareStatus
self.sensitiveDataStatus = sensitiveDataStatus
self.gpcStatus = gpcStatus
}

public override func isEqual(_ object: Any?) -> Bool {
guard let other = object as? Statuses else { return false }

return other.rejectedAny == rejectedAny &&
other.consentedToAll == consentedToAll &&
other.consentedToAny == consentedToAny &&
other.hasConsentData == hasConsentData &&
other.sellStatus == sellStatus &&
other.shareStatus == shareStatus &&
other.sensitiveDataStatus == sensitiveDataStatus &&
other.gpcStatus == gpcStatus
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,6 @@ struct CCPAChoiceResponse: Equatable {
let GPPData: SPJson
}

struct USNatChoiceResponse: Equatable, Decodable {
let uuid: String
let consentStrings: [SPUSNatConsent.ConsentString]
let categories: [String]
let dateCreated, expirationDate: SPDate
let webConsentPayload: SPWebConsentPayload?
let consentStatus: ConsentStatus
let GPPData: SPJson?
}

extension CCPAChoiceResponse: Decodable {
enum CodingKeys: CodingKey {
case uuid, dateCreated, consentedAll, rejectedAll,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ struct ConsentStatusResponse: Decodable, Equatable {
let consentStrings: [SPUSNatConsent.ConsentString]
let dateCreated, expirationDate: SPDate
let consentStatus: ConsentStatus
let categories: [String]
let webConsentPayload: SPWebConsentPayload?
let GPPData: SPJson?
let userConsents: SPUSNatConsent.UserConsents
}

let gdpr: GDPR?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ extension Consent: Codable {
webConsentPayload: consents.webConsentPayload,
lastMessage: LastMessageData(from: messageMetaData),
categories: consents.categories,
vendors: consents.vendors,
consentStatus: consents.consentStatus,
GPPData: consents.GPPData
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ typealias CCPAPrivacyManagerViewHandler = (Result<CCPAPrivacyManagerViewResponse
typealias MessageHandler = (Result<Message, SPError>) -> Void
typealias CCPAConsentHandler = (Result<CCPAChoiceResponse, SPError>) -> Void
typealias GDPRConsentHandler = (Result<GDPRChoiceResponse, SPError>) -> Void
typealias USNatConsentHandler = (Result<USNatChoiceResponse, SPError>) -> Void
typealias USNatConsentHandler = (Result<SPUSNatConsent, SPError>) -> Void
typealias ConsentHandler<T: Decodable & Equatable> = (Result<(SPJson, T), SPError>) -> Void
typealias AddOrDeleteCustomConsentHandler = (Result<AddOrDeleteCustomConsentResponse, SPError>) -> Void
typealias ConsentStatusHandler = (Result<ConsentStatusResponse, SPError>) -> Void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ extension SPSampleable {

class SourcepointClientCoordinator: SPClientCoordinator {
struct State: Codable {
static let version = 3
static let version = 4

struct GDPRMetaData: Codable, SPSampleable, Equatable {
var additionsChangeDate = SPDate.now()
Expand Down Expand Up @@ -560,7 +560,8 @@ class SourcepointClientCoordinator: SPClientCoordinator {
expirationDate: usnat.expirationDate,
consentStrings: usnat.consentStrings,
webConsentPayload: usnat.webConsentPayload,
categories: usnat.categories,
categories: usnat.userConsents.categories,
vendors: usnat.userConsents.vendors,
consentStatus: usnat.consentStatus,
GPPData: usnat.GPPData
)
Expand Down Expand Up @@ -839,7 +840,7 @@ class SourcepointClientCoordinator: SPClientCoordinator {

func postChoice(
_ action: SPAction,
handler: @escaping (Result<USNatChoiceResponse, SPError>) -> Void
handler: @escaping (Result<SPUSNatConsent, SPError>) -> Void
) {
spClient.postUSNatAction(
actionType: action.type,
Expand Down Expand Up @@ -932,7 +933,7 @@ class SourcepointClientCoordinator: SPClientCoordinator {

func handleUSNatPostChoice(
_ action: SPAction,
_ postResponse: USNatChoiceResponse
_ postResponse: SPUSNatConsent
) {
state.usnat = SPUSNatConsent(
uuid: postResponse.uuid,
Expand All @@ -942,6 +943,7 @@ class SourcepointClientCoordinator: SPClientCoordinator {
consentStrings: postResponse.consentStrings,
webConsentPayload: postResponse.webConsentPayload,
categories: postResponse.categories,
vendors: postResponse.vendors,
consentStatus: postResponse.consentStatus,
GPPData: postResponse.GPPData
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ public func containQueryParam(_ name: String, withValue value: String) -> Predic
guard let actual = try actual.evaluate(),
let params = actual.queryParams
else {
return PredicateResult(bool: false, message: .fail("could not get query params from URL(\(try? actual.evaluate()?.absoluteString as Any))"))
return PredicateResult(bool: false, message: .fail("could not get query params from URL(\((try? actual.evaluate()?.absoluteString) as Any))"))
}
var pass = false
var message = ""
Expand Down
Loading
Loading