mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
Verifies downloaded app’s permissions match source
Renames source JSON permissions key to “appPermissions” in order to preserve backwards compatibility, since we’ve changed the schema for permissions.
This commit is contained in:
@@ -94,6 +94,16 @@ struct VerificationError: ALTLocalizedError {
|
||||
}
|
||||
}
|
||||
|
||||
import RegexBuilder
|
||||
|
||||
private extension ALTEntitlement
|
||||
{
|
||||
static var ignoredEntitlements: Set<ALTEntitlement> = [
|
||||
.applicationIdentifier,
|
||||
.teamIdentifier
|
||||
]
|
||||
}
|
||||
|
||||
@objc(VerifyAppOperation)
|
||||
final class VerifyAppOperation: ResultOperation<Void>
|
||||
{
|
||||
@@ -146,6 +156,11 @@ final class VerifyAppOperation: ResultOperation<Void>
|
||||
try await self.verifyHash(of: app, at: ipaURL, matches: appVersion)
|
||||
try await self.verifyDownloadedVersion(of: app, matches: appVersion)
|
||||
|
||||
if let storeApp = await self.context.$appVersion.app
|
||||
{
|
||||
try await self.verifyPermissions(of: app, match: storeApp)
|
||||
}
|
||||
|
||||
self.finish(.success(()))
|
||||
}
|
||||
catch
|
||||
@@ -183,4 +198,81 @@ private extension VerifyAppOperation
|
||||
|
||||
guard version == app.version else { throw VerificationError.mismatchedVersion(app.version, expectedVersion: version, app: app) }
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func verifyPermissions(of app: ALTApplication, @AsyncManaged match storeApp: StoreApp) async throws -> [any ALTAppPermission]
|
||||
{
|
||||
// Entitlements
|
||||
var allEntitlements = Set(app.entitlements.keys)
|
||||
for appExtension in app.appExtensions
|
||||
{
|
||||
allEntitlements.formUnion(appExtension.entitlements.keys)
|
||||
}
|
||||
|
||||
// Filter out ignored entitlements.
|
||||
allEntitlements = allEntitlements.filter { !ALTEntitlement.ignoredEntitlements.contains($0) }
|
||||
|
||||
|
||||
// Background Modes
|
||||
// App extensions can't have background modes, so don't need to worry about them.
|
||||
let allBackgroundModes: Set<ALTAppBackgroundMode>
|
||||
if let backgroundModes = app.bundle.infoDictionary?[Bundle.Info.backgroundModes] as? [String]
|
||||
{
|
||||
let backgroundModes = backgroundModes.lazy.map { ALTAppBackgroundMode($0) }
|
||||
allBackgroundModes = Set(backgroundModes)
|
||||
}
|
||||
else
|
||||
{
|
||||
allBackgroundModes = []
|
||||
}
|
||||
|
||||
|
||||
// Privacy
|
||||
let allPrivacyPermissions: Set<ALTAppPrivacyPermission>
|
||||
if #available(iOS 16, *)
|
||||
{
|
||||
let regex = Regex {
|
||||
"NS"
|
||||
|
||||
// Capture permission "name"
|
||||
Capture {
|
||||
OneOrMore(.anyGraphemeCluster)
|
||||
}
|
||||
|
||||
"UsageDescription"
|
||||
|
||||
// Optional suffix
|
||||
Optionally(OneOrMore(.anyGraphemeCluster))
|
||||
}
|
||||
|
||||
let privacyPermissions = ([app] + app.appExtensions).flatMap { (app) in
|
||||
let permissions = app.bundle.infoDictionary?.keys.compactMap { key -> ALTAppPrivacyPermission? in
|
||||
guard let match = key.wholeMatch(of: regex) else { return nil }
|
||||
|
||||
let permission = ALTAppPrivacyPermission(rawValue: String(match.1))
|
||||
return permission
|
||||
} ?? []
|
||||
|
||||
return permissions
|
||||
}
|
||||
|
||||
allPrivacyPermissions = Set(privacyPermissions)
|
||||
}
|
||||
else
|
||||
{
|
||||
allPrivacyPermissions = []
|
||||
}
|
||||
|
||||
|
||||
// Verify permissions.
|
||||
let sourcePermissions: Set<AnyHashable> = Set(await $storeApp.perform { $0.permissions.map { AnyHashable($0.permission) } })
|
||||
let localPermissions: [any ALTAppPermission] = Array(allEntitlements) + Array(allBackgroundModes) + Array(allPrivacyPermissions)
|
||||
|
||||
// To pass: EVERY permission in localPermissions must also appear in sourcePermissions.
|
||||
// If there is a single missing permission, throw error.
|
||||
let missingPermissions: [any ALTAppPermission] = localPermissions.filter { !sourcePermissions.contains(AnyHashable($0)) }
|
||||
guard missingPermissions.isEmpty else { throw VerificationError.undeclaredPermissions(missingPermissions, app: app) }
|
||||
|
||||
return localPermissions
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user