Skip to content

Commit

Permalink
Merge pull request #555 from SourcePointUSA/DIA-3558_expand_usnat_con…
Browse files Browse the repository at this point in the history
…sent_object

DIA-3558 expand usnat consent object
  • Loading branch information
andresilveirah authored Mar 1, 2024
2 parents c1c2fcf + df43b2e commit a363b54
Show file tree
Hide file tree
Showing 11 changed files with 291 additions and 132 deletions.
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

0 comments on commit a363b54

Please sign in to comment.