Revises appPermissions JSON format

• Split into `entitlements` and `privacy` sections
• `entitlements` is an array of entitlement keys
• `privacy` is a dictionary mapping UsageDescription keys to their descriptions
This commit is contained in:
Riley Testut
2023-10-10 14:47:00 -05:00
committed by Magesh K
parent cd42cc827f
commit 34c503da4b
5 changed files with 197 additions and 73 deletions

View File

@@ -73,7 +73,7 @@ public extension ALTAppPermissionType
}
@objc(AppPermission) @dynamicMemberLookup
public class AppPermission: NSManagedObject, Decodable, Fetchable
public class AppPermission: NSManagedObject, Fetchable
{
/* Properties */
@NSManaged public var type: ALTAppPermissionType
@@ -108,37 +108,13 @@ public class AppPermission: NSManagedObject, Decodable, Fetchable
super.init(entity: entity, insertInto: context)
}
private enum CodingKeys: String, CodingKey
convenience init(permission: String, usageDescription: String?, type: ALTAppPermissionType, context: NSManagedObjectContext)
{
case name
case usageDescription
}
public required init(from decoder: Decoder) throws
{
guard let context = decoder.managedObjectContext else { preconditionFailure("Decoder must have non-nil NSManagedObjectContext.") }
self.init(entity: AppPermission.entity(), insertInto: context)
super.init(entity: AppPermission.entity(), insertInto: context)
do
{
let container = try decoder.container(keyedBy: CodingKeys.self)
self._permission = try container.decode(String.self, forKey: .name)
self.usageDescription = try container.decodeIfPresent(String.self, forKey: .usageDescription)
// Will be updated from StoreApp.
self.type = .unknown
}
catch
{
if let context = self.managedObjectContext
{
context.delete(self)
}
throw error
}
self._permission = permission
self.usageDescription = usageDescription
self.type = type
}
}
@@ -160,3 +136,113 @@ public extension AppPermission
}
}
}
private struct AnyDecodable: Decodable
{
init(from decoder: Decoder) throws
{
}
}
internal struct AppPermissions: Decodable
{
var entitlements: [AppPermission] = []
var privacy: [AppPermission] = []
private enum CodingKeys: String, CodingKey, Decodable
{
case entitlements
case privacy
// Legacy
case name
case usageDescription
}
init(from decoder: Decoder) throws
{
guard let context = decoder.managedObjectContext else { preconditionFailure("Decoder must have non-nil NSManagedObjectContext.") }
let container = try decoder.container(keyedBy: CodingKeys.self)
self.entitlements = try self.parseEntitlements(from: container, into: context)
self.privacy = try self.parsePrivacyPermissions(from: container, into: context)
}
private func parseEntitlements(from container: KeyedDecodingContainer<CodingKeys>, into context: NSManagedObjectContext) throws -> [AppPermission]
{
guard container.contains(.entitlements) else { return [] }
do
{
do
{
// Legacy
// Must parse as [String: String], NOT [CodingKeys: String], to avoid incorrect DecodingError.typeMismatch error.
let rawEntitlements = try container.decode([[String: String]].self, forKey: .entitlements)
let entitlements = try rawEntitlements.compactMap { (dictionary) -> AppPermission? in
guard let name = dictionary[CodingKeys.name.rawValue] else {
let context = DecodingError.Context(codingPath: container.codingPath, debugDescription: "Legacy entitlements must have `name` key.")
throw DecodingError.keyNotFound(CodingKeys.name, context)
}
let entitlement = AppPermission(permission: name, usageDescription: nil, type: .entitlement, context: context)
return entitlement
}
return entitlements
}
catch DecodingError.typeMismatch
{
// Detailed
// AnyDecodable ensures we're forward-compatible with any values we may later require for entitlement permissions.
let rawEntitlements = try container.decode([String: AnyDecodable?].self, forKey: .entitlements)
let entitlements = rawEntitlements.map { AppPermission(permission: $0.key, usageDescription: nil, type: .entitlement, context: context) }
return entitlements
}
}
catch DecodingError.typeMismatch
{
// Default
let rawEntitlements = try container.decode([String].self, forKey: .entitlements)
let entitlements = rawEntitlements.map { AppPermission(permission: $0, usageDescription: nil, type: .entitlement, context: context) }
return entitlements
}
}
private func parsePrivacyPermissions(from container: KeyedDecodingContainer<CodingKeys>, into context: NSManagedObjectContext) throws -> [AppPermission]
{
guard container.contains(.privacy) else { return [] }
do
{
// Legacy
// Must parse as [String: String], NOT [CodingKeys: String], to avoid incorrect DecodingError.typeMismatch error.
let rawPermissions = try container.decode([[String: String]].self, forKey: .privacy)
let permissions = try rawPermissions.compactMap { (dictionary) -> AppPermission? in
guard let name = dictionary[CodingKeys.name.rawValue] else {
let context = DecodingError.Context(codingPath: container.codingPath, debugDescription: "Legacy privacy permissions must have `name` key.")
throw DecodingError.keyNotFound(CodingKeys.name, context)
}
let usageDescription = dictionary[CodingKeys.usageDescription.rawValue]
let permission = AppPermission(permission: name, usageDescription: usageDescription, type: .privacy, context: context)
return permission
}
return permissions
}
catch DecodingError.typeMismatch
{
// Default
let rawPermissions = try container.decode([String: String?].self, forKey: .privacy)
let permissions = rawPermissions.map { AppPermission(permission: $0, usageDescription: $1, type: .privacy, context: context) }
return permissions
}
}
}

View File

@@ -23,12 +23,6 @@ public extension StoreApp
#endif
static let dolphinAppID = "me.oatmealdome.dolphinios-njb"
private struct AppPermissions: Decodable
{
var entitlements: [AppPermission]?
var privacy: [AppPermission]?
}
}
@objc
@@ -299,10 +293,7 @@ public class StoreApp: NSManagedObject, Decodable, Fetchable
if let appPermissions = try container.decodeIfPresent(AppPermissions.self, forKey: .permissions)
{
appPermissions.entitlements?.forEach { $0.type = .entitlement }
appPermissions.privacy?.forEach { $0.type = .privacy }
let allPermissions = (appPermissions.entitlements ?? []) + (appPermissions.privacy ?? [])
let allPermissions = appPermissions.entitlements + appPermissions.privacy
for permission in allPermissions
{
permission.appBundleID = self.bundleIdentifier