mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 11:07:10 +00:00
Inspector parses uses-permission entries from `aapt dump permissions` and the granter shells out to `adb shell pm grant`. Both are pluggable so tests can drive logic without aapt or a device. Granter failures become warnings rather than errors — Android refuses non-runtime permissions and we'd rather keep going than abort the run.
126 lines
5.2 KiB
Go
126 lines
5.2 KiB
Go
package permissions
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os/exec"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// Inspector returns the list of uses-permission entries declared by the APK.
|
|
type Inspector func(ctx context.Context, apkPath string) ([]string, error)
|
|
|
|
// Granter grants a single permission to the given package on the connected
|
|
// device. Implementations typically wrap `adb shell pm grant`.
|
|
type Granter func(ctx context.Context, packageName, permission string) error
|
|
|
|
// GrantDangerous filters declared permissions to the dangerous set and grants
|
|
// each one via the supplied granter. Errors from individual grants are
|
|
// collected as warnings rather than aborting — Android refuses to grant
|
|
// non-runtime permissions and we prefer to soldier on.
|
|
func GrantDangerous(
|
|
ctx context.Context,
|
|
apkPath, packageName string,
|
|
inspector Inspector,
|
|
granter Granter,
|
|
) (granted []string, warnings []string, err error) {
|
|
declared, err := inspector(ctx, apkPath)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("inspect permissions: %w", err)
|
|
}
|
|
for _, permission := range declared {
|
|
if !IsDangerous(permission) {
|
|
continue
|
|
}
|
|
if err := granter(ctx, packageName, permission); err != nil {
|
|
warnings = append(warnings, fmt.Sprintf("%s: %v", permission, err))
|
|
continue
|
|
}
|
|
granted = append(granted, permission)
|
|
}
|
|
return granted, warnings, nil
|
|
}
|
|
|
|
var aaptPermissionPattern = regexp.MustCompile(`uses-permission:\s+name='([^']+)'`)
|
|
|
|
// AaptInspector shells out to `aapt dump permissions` to list permissions.
|
|
func AaptInspector(ctx context.Context, apkPath string) ([]string, error) {
|
|
output, err := exec.CommandContext(ctx, "aapt", "dump", "permissions", apkPath).Output()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("aapt dump permissions: %w", err)
|
|
}
|
|
var permissions []string
|
|
for _, match := range aaptPermissionPattern.FindAllStringSubmatch(string(output), -1) {
|
|
permissions = append(permissions, match[1])
|
|
}
|
|
return permissions, nil
|
|
}
|
|
|
|
// AdbGranter returns a Granter that runs `adb shell pm grant` against the
|
|
// supplied device serial (empty = default device).
|
|
func AdbGranter(adbPath, deviceSerial string) Granter {
|
|
return func(ctx context.Context, packageName, permission string) error {
|
|
arguments := []string{"shell", "pm", "grant", packageName, permission}
|
|
if deviceSerial != "" {
|
|
arguments = append([]string{"-s", deviceSerial}, arguments...)
|
|
}
|
|
command := exec.CommandContext(ctx, adbPath, arguments...)
|
|
output, err := command.CombinedOutput()
|
|
if err != nil {
|
|
return fmt.Errorf("adb pm grant %s: %w (%s)", permission, err, strings.TrimSpace(string(output)))
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// dangerousPermissions captures Android's PROTECTION_DANGEROUS set as of
|
|
// API 34. New runtime permissions added in later releases should be appended
|
|
// here when needed.
|
|
var dangerousPermissions = map[string]bool{
|
|
"android.permission.READ_CALENDAR": true,
|
|
"android.permission.WRITE_CALENDAR": true,
|
|
"android.permission.CAMERA": true,
|
|
"android.permission.READ_CONTACTS": true,
|
|
"android.permission.WRITE_CONTACTS": true,
|
|
"android.permission.GET_ACCOUNTS": true,
|
|
"android.permission.ACCESS_FINE_LOCATION": true,
|
|
"android.permission.ACCESS_COARSE_LOCATION": true,
|
|
"android.permission.ACCESS_BACKGROUND_LOCATION": true,
|
|
"android.permission.RECORD_AUDIO": true,
|
|
"android.permission.READ_PHONE_STATE": true,
|
|
"android.permission.READ_PHONE_NUMBERS": true,
|
|
"android.permission.CALL_PHONE": true,
|
|
"android.permission.ANSWER_PHONE_CALLS": true,
|
|
"android.permission.READ_CALL_LOG": true,
|
|
"android.permission.WRITE_CALL_LOG": true,
|
|
"android.permission.ADD_VOICEMAIL": true,
|
|
"android.permission.USE_SIP": true,
|
|
"android.permission.PROCESS_OUTGOING_CALLS": true,
|
|
"android.permission.BODY_SENSORS": true,
|
|
"android.permission.SEND_SMS": true,
|
|
"android.permission.RECEIVE_SMS": true,
|
|
"android.permission.READ_SMS": true,
|
|
"android.permission.RECEIVE_WAP_PUSH": true,
|
|
"android.permission.RECEIVE_MMS": true,
|
|
"android.permission.READ_EXTERNAL_STORAGE": true,
|
|
"android.permission.WRITE_EXTERNAL_STORAGE": true,
|
|
"android.permission.ACCESS_MEDIA_LOCATION": true,
|
|
"android.permission.ACTIVITY_RECOGNITION": true,
|
|
"android.permission.POST_NOTIFICATIONS": true,
|
|
"android.permission.NEARBY_WIFI_DEVICES": true,
|
|
"android.permission.READ_MEDIA_IMAGES": true,
|
|
"android.permission.READ_MEDIA_VIDEO": true,
|
|
"android.permission.READ_MEDIA_AUDIO": true,
|
|
"android.permission.READ_MEDIA_VISUAL_USER_SELECTED": true,
|
|
"android.permission.BLUETOOTH_CONNECT": true,
|
|
"android.permission.BLUETOOTH_ADVERTISE": true,
|
|
"android.permission.BLUETOOTH_SCAN": true,
|
|
"android.permission.UWB_RANGING": true,
|
|
"android.permission.BODY_SENSORS_BACKGROUND": true,
|
|
}
|
|
|
|
func IsDangerous(permission string) bool {
|
|
return dangerousPermissions[permission]
|
|
}
|