mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-02-27 12:22:43 -08:00
update
This commit is contained in:
310
.agents/skills/ios-simulator-skill/scripts/privacy_manager.py
Normal file
310
.agents/skills/ios-simulator-skill/scripts/privacy_manager.py
Normal file
@@ -0,0 +1,310 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
iOS Privacy & Permissions Manager
|
||||
|
||||
Grant/revoke app permissions for testing permission flows.
|
||||
Supports 13+ services with audit trail tracking.
|
||||
|
||||
Usage: python scripts/privacy_manager.py --grant camera --bundle-id com.app
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
from common import resolve_udid
|
||||
|
||||
|
||||
class PrivacyManager:
|
||||
"""Manages iOS app privacy and permissions."""
|
||||
|
||||
# Supported services
|
||||
SUPPORTED_SERVICES = {
|
||||
"camera": "Camera access",
|
||||
"microphone": "Microphone access",
|
||||
"location": "Location services",
|
||||
"contacts": "Contacts access",
|
||||
"photos": "Photos library access",
|
||||
"calendar": "Calendar access",
|
||||
"health": "Health data access",
|
||||
"reminders": "Reminders access",
|
||||
"motion": "Motion & fitness",
|
||||
"keyboard": "Keyboard access",
|
||||
"mediaLibrary": "Media library",
|
||||
"calls": "Call history",
|
||||
"siri": "Siri access",
|
||||
}
|
||||
|
||||
def __init__(self, udid: str | None = None):
|
||||
"""Initialize privacy manager.
|
||||
|
||||
Args:
|
||||
udid: Optional device UDID (auto-detects booted simulator if None)
|
||||
"""
|
||||
self.udid = udid
|
||||
|
||||
def grant_permission(
|
||||
self,
|
||||
bundle_id: str,
|
||||
service: str,
|
||||
scenario: str | None = None,
|
||||
step: int | None = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Grant permission for app.
|
||||
|
||||
Args:
|
||||
bundle_id: App bundle ID
|
||||
service: Service name (camera, microphone, location, etc.)
|
||||
scenario: Test scenario name for audit trail
|
||||
step: Step number in test scenario
|
||||
|
||||
Returns:
|
||||
Success status
|
||||
"""
|
||||
if service not in self.SUPPORTED_SERVICES:
|
||||
print(f"Error: Unknown service '{service}'")
|
||||
print(f"Supported: {', '.join(self.SUPPORTED_SERVICES.keys())}")
|
||||
return False
|
||||
|
||||
cmd = ["xcrun", "simctl", "privacy"]
|
||||
|
||||
if self.udid:
|
||||
cmd.append(self.udid)
|
||||
else:
|
||||
cmd.append("booted")
|
||||
|
||||
cmd.extend(["grant", service, bundle_id])
|
||||
|
||||
try:
|
||||
subprocess.run(cmd, capture_output=True, check=True)
|
||||
|
||||
# Log audit entry
|
||||
self._log_audit("grant", bundle_id, service, scenario, step)
|
||||
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
def revoke_permission(
|
||||
self,
|
||||
bundle_id: str,
|
||||
service: str,
|
||||
scenario: str | None = None,
|
||||
step: int | None = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Revoke permission for app.
|
||||
|
||||
Args:
|
||||
bundle_id: App bundle ID
|
||||
service: Service name
|
||||
scenario: Test scenario name for audit trail
|
||||
step: Step number in test scenario
|
||||
|
||||
Returns:
|
||||
Success status
|
||||
"""
|
||||
if service not in self.SUPPORTED_SERVICES:
|
||||
print(f"Error: Unknown service '{service}'")
|
||||
return False
|
||||
|
||||
cmd = ["xcrun", "simctl", "privacy"]
|
||||
|
||||
if self.udid:
|
||||
cmd.append(self.udid)
|
||||
else:
|
||||
cmd.append("booted")
|
||||
|
||||
cmd.extend(["revoke", service, bundle_id])
|
||||
|
||||
try:
|
||||
subprocess.run(cmd, capture_output=True, check=True)
|
||||
|
||||
# Log audit entry
|
||||
self._log_audit("revoke", bundle_id, service, scenario, step)
|
||||
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
def reset_permission(
|
||||
self,
|
||||
bundle_id: str,
|
||||
service: str,
|
||||
scenario: str | None = None,
|
||||
step: int | None = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Reset permission to default.
|
||||
|
||||
Args:
|
||||
bundle_id: App bundle ID
|
||||
service: Service name
|
||||
scenario: Test scenario name for audit trail
|
||||
step: Step number in test scenario
|
||||
|
||||
Returns:
|
||||
Success status
|
||||
"""
|
||||
if service not in self.SUPPORTED_SERVICES:
|
||||
print(f"Error: Unknown service '{service}'")
|
||||
return False
|
||||
|
||||
cmd = ["xcrun", "simctl", "privacy"]
|
||||
|
||||
if self.udid:
|
||||
cmd.append(self.udid)
|
||||
else:
|
||||
cmd.append("booted")
|
||||
|
||||
cmd.extend(["reset", service, bundle_id])
|
||||
|
||||
try:
|
||||
subprocess.run(cmd, capture_output=True, check=True)
|
||||
|
||||
# Log audit entry
|
||||
self._log_audit("reset", bundle_id, service, scenario, step)
|
||||
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _log_audit(
|
||||
action: str,
|
||||
bundle_id: str,
|
||||
service: str,
|
||||
scenario: str | None = None,
|
||||
step: int | None = None,
|
||||
) -> None:
|
||||
"""Log permission change to audit trail (for test tracking).
|
||||
|
||||
Args:
|
||||
action: grant, revoke, or reset
|
||||
bundle_id: App bundle ID
|
||||
service: Service name
|
||||
scenario: Test scenario name
|
||||
step: Step number
|
||||
"""
|
||||
# Could write to file, but for now just log to stdout for transparency
|
||||
timestamp = datetime.now().isoformat()
|
||||
location = f" (step {step})" if step else ""
|
||||
scenario_info = f" in {scenario}" if scenario else ""
|
||||
print(
|
||||
f"[Audit] {timestamp}: {action.upper()} {service} for {bundle_id}{scenario_info}{location}"
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
parser = argparse.ArgumentParser(description="Manage iOS app privacy and permissions")
|
||||
|
||||
# Required
|
||||
parser.add_argument("--bundle-id", required=True, help="App bundle ID (e.g., com.example.app)")
|
||||
|
||||
# Action (mutually exclusive)
|
||||
action_group = parser.add_mutually_exclusive_group(required=True)
|
||||
action_group.add_argument(
|
||||
"--grant",
|
||||
help="Grant permission (service name or comma-separated list)",
|
||||
)
|
||||
action_group.add_argument(
|
||||
"--revoke", help="Revoke permission (service name or comma-separated list)"
|
||||
)
|
||||
action_group.add_argument(
|
||||
"--reset",
|
||||
help="Reset permission to default (service name or comma-separated list)",
|
||||
)
|
||||
action_group.add_argument(
|
||||
"--list",
|
||||
action="store_true",
|
||||
help="List all supported services",
|
||||
)
|
||||
|
||||
# Test tracking
|
||||
parser.add_argument(
|
||||
"--scenario",
|
||||
help="Test scenario name for audit trail",
|
||||
)
|
||||
parser.add_argument("--step", type=int, help="Step number in test scenario")
|
||||
|
||||
# Device
|
||||
parser.add_argument(
|
||||
"--udid",
|
||||
help="Device UDID (auto-detects booted simulator if not provided)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# List supported services
|
||||
if args.list:
|
||||
print("Supported Privacy Services:\n")
|
||||
for service, description in PrivacyManager.SUPPORTED_SERVICES.items():
|
||||
print(f" {service:<15} - {description}")
|
||||
print()
|
||||
print("Examples:")
|
||||
print(" python scripts/privacy_manager.py --grant camera --bundle-id com.app")
|
||||
print(" python scripts/privacy_manager.py --revoke location --bundle-id com.app")
|
||||
print(" python scripts/privacy_manager.py --grant camera,photos --bundle-id com.app")
|
||||
sys.exit(0)
|
||||
|
||||
# Resolve UDID with auto-detection
|
||||
try:
|
||||
udid = resolve_udid(args.udid)
|
||||
except RuntimeError as e:
|
||||
print(f"Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
manager = PrivacyManager(udid=udid)
|
||||
|
||||
# Parse service names (support comma-separated list)
|
||||
if args.grant:
|
||||
services = [s.strip() for s in args.grant.split(",")]
|
||||
action = "grant"
|
||||
action_fn = manager.grant_permission
|
||||
elif args.revoke:
|
||||
services = [s.strip() for s in args.revoke.split(",")]
|
||||
action = "revoke"
|
||||
action_fn = manager.revoke_permission
|
||||
else: # reset
|
||||
services = [s.strip() for s in args.reset.split(",")]
|
||||
action = "reset"
|
||||
action_fn = manager.reset_permission
|
||||
|
||||
# Execute action for each service
|
||||
all_success = True
|
||||
for service in services:
|
||||
if service not in PrivacyManager.SUPPORTED_SERVICES:
|
||||
print(f"Error: Unknown service '{service}'")
|
||||
all_success = False
|
||||
continue
|
||||
|
||||
success = action_fn(
|
||||
args.bundle_id,
|
||||
service,
|
||||
scenario=args.scenario,
|
||||
step=args.step,
|
||||
)
|
||||
|
||||
if success:
|
||||
description = PrivacyManager.SUPPORTED_SERVICES[service]
|
||||
print(f"✓ {action.capitalize()} {service}: {description}")
|
||||
else:
|
||||
print(f"✗ Failed to {action} {service}")
|
||||
all_success = False
|
||||
|
||||
if not all_success:
|
||||
sys.exit(1)
|
||||
|
||||
# Summary
|
||||
if len(services) > 1:
|
||||
print(f"\nPermissions {action}ed: {', '.join(services)}")
|
||||
|
||||
if args.scenario:
|
||||
print(f"Test scenario: {args.scenario}" + (f" (step {args.step})" if args.step else ""))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user