ביקורת הגישה לנתונים

כדי לקבל תובנות לגבי האופן שבו האפליקציה והרכיבים התלויים שלה ניגשים לנתונים פרטיים של משתמשים, אתם יכולים לבצע בקרת הרשאות גישה לנתונים. התהליך הזה, שזמין במכשירים עם Android מגרסה 11 (רמת API‏ 30) ואילך, מאפשר לכם לזהות טוב יותר גישה לא צפויה לנתונים. האפליקציה יכולה לרשום מופע של AppOpsManager.OnOpNotedCallback, שיכול לבצע פעולות בכל פעם שמתרחש אחד מהאירועים הבאים:

בקרת הרשאות הגישה לנתונים מופעלת בשרשור שבו מתבצעת בקשת הנתונים. כלומר, אם ערכת SDK או ספרייה של צד שלישי באפליקציה שלכם שולחת קריאה ל-API שניגש לנתונים פרטיים, בקרת הרשאות גישה לנתונים מאפשרת ל-OnOpNotedCallback לבדוק מידע על הקריאה. בדרך כלל, אובייקט הקריאה החוזרת הזה יכול לזהות אם הקריאה הגיעה מהאפליקציה או מה-SDK על ידי בדיקת הסטטוס הנוכחי של האפליקציה, כמו דוח הקריסות של השרשור הנוכחי.

גישה לנתונים ביומן

כדי לבצע בקרת הרשאות גישה לנתונים באמצעות מופע של AppOpsManager.OnOpNotedCallback, צריך להטמיע את הלוגיקה של הקריאה החוזרת ברכיב שבו רוצים לבצע את הבקרה, למשל ב-method onCreate() של פעילות או ב-method onCreate() של אפליקציה.

בקטע הקוד הבא מוגדר AppOpsManager.OnOpNotedCallback לביקורת על הגישה לנתונים בפעילות אחת:

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    val appOpsCallback = object : AppOpsManager.OnOpNotedCallback() {
        private fun logPrivateDataAccess(opCode: String, trace: String) {
            Log.i(MY_APP_TAG, "Private data accessed. " +
                    "Operation: $opCode\nStack Trace:\n$trace")
        }

        override fun onNoted(syncNotedAppOp: SyncNotedAppOp) {
            logPrivateDataAccess(
                    syncNotedAppOp.op, Throwable().stackTrace.toString())
        }

        override fun onSelfNoted(syncNotedAppOp: SyncNotedAppOp) {
            logPrivateDataAccess(
                    syncNotedAppOp.op, Throwable().stackTrace.toString())
        }

        override fun onAsyncNoted(asyncNotedAppOp: AsyncNotedAppOp) {
            logPrivateDataAccess(asyncNotedAppOp.op, asyncNotedAppOp.message)
        }
    }

    val appOpsManager =
            getSystemService(AppOpsManager::class.java) as AppOpsManager
    appOpsManager.setOnOpNotedCallback(mainExecutor, appOpsCallback)
}

Java

@Override
public void onCreate(@Nullable Bundle savedInstanceState,
        @Nullable PersistableBundle persistentState) {
    AppOpsManager.OnOpNotedCallback appOpsCallback =
            new AppOpsManager.OnOpNotedCallback() {
        private void logPrivateDataAccess(String opCode, String trace) {
            Log.i(MY_APP_TAG, "Private data accessed. " +
                    "Operation: $opCode\nStack Trace:\n$trace");
        }

        @Override
        public void onNoted(@NonNull SyncNotedAppOp syncNotedAppOp) {
            logPrivateDataAccess(syncNotedAppOp.getOp(),
                    Arrays.toString(new Throwable().getStackTrace()));
        }

        @Override
        public void onSelfNoted(@NonNull SyncNotedAppOp syncNotedAppOp) {
            logPrivateDataAccess(syncNotedAppOp.getOp(),
                    Arrays.toString(new Throwable().getStackTrace()));
        }

        @Override
        public void onAsyncNoted(@NonNull AsyncNotedAppOp asyncNotedAppOp) {
            logPrivateDataAccess(asyncNotedAppOp.getOp(),
                    asyncNotedAppOp.getMessage());
        }
    };

    AppOpsManager appOpsManager = getSystemService(AppOpsManager.class);
    if (appOpsManager != null) {
        appOpsManager.setOnOpNotedCallback(getMainExecutor(), appOpsCallback);
    }
}

השיטות onAsyncNoted() ו-onSelfNoted() נקראות במצבים ספציפיים:

  • onAsyncNoted() מופעלת אם הגישה לנתונים לא מתרחשת במהלך הקריאה ל-API של האפליקציה. הדוגמה הנפוצה ביותר היא כשהאפליקציה רושמת מאזין והגישה לנתונים מתרחשת בכל פעם שהקריאה החוזרת (callback) של המאזין מופעלת.

    הארגומנט AsyncNotedOp שמועבר אל onAsyncNoted() מכיל method שנקראת getMessage(). השיטה הזו מספקת מידע נוסף על הגישה לנתונים. במקרה של קריאות חוזרות (callbacks) של מיקום, ההודעה מכילה את ה-system-identity-hash של המאזין.

  • הפונקציה onSelfNoted() נקראת במקרים נדירים מאוד שבהם אפליקציה מעבירה את ה-UID שלה אל noteOp().

בדיקת הגישה לנתונים לפי תג שיוך (Attribution)

יכול להיות שלאפליקציה שלכם יש כמה תרחישי שימוש עיקריים, כמו לאפשר למשתמשים לצלם תמונות ולשתף אותן עם אנשי הקשר שלהם. אם אתם מפתחים אפליקציה רב-תכליתית, אתם יכולים להחיל תג שיוך (Attribution) על כל חלק באפליקציה כשאתם בודקים את הגישה שלה לנתונים. ההקשר של attributionTag מוחזר באובייקטים שמועברים לקריאות ל-onNoted(). כך קל יותר לעקוב אחרי הגישה לנתונים בחלקים הלוגיים של הקוד.

כדי להגדיר תגי שיוך באפליקציה, פועלים לפי השלבים שמפורטים בקטעים הבאים.

הצהרה על תגי שיוך (Attribution) במניפסט

אם האפליקציה שלכם מטרגטת ל-Android 12 (רמת API 31) ומעלה, אתם צריכים להצהיר על תגי שיוך בקובץ המניפסט של האפליקציה, בפורמט שמוצג בקטע הקוד הבא. אם תנסו להשתמש בתג שיוך שלא הצהרתם עליו בקובץ המניפסט של האפליקציה, המערכת תיצור בשבילכם תג null ותתעד הודעה ב-Logcat.

<manifest ...>
    <!-- The value of "android:tag" must be a literal string, and the
         value of "android:label" must be a resource. The value of
         "android:label" is user-readable. -->
    <attribution android:tag="sharePhotos"
                 android:label="@string/share_photos_attribution_label" />
    ...
</manifest>

יצירת תגי שיוך (Attribution)

ב-method onCreate() של הפעילות שבה אתם ניגשים לנתונים, כמו הפעילות שבה אתם מבקשים מיקום או ניגשים לרשימת אנשי הקשר של המשתמש, קוראים ל-createAttributionContext() ומעבירים את תג השיוך שאתם רוצים לשייך לחלק מהאפליקציה.

בקטע הקוד הבא אפשר לראות איך יוצרים תג שיוך (Attribution) לחלק באפליקציה שבו משתפים מיקום של תמונה:

Kotlin

class SharePhotoLocationActivity : AppCompatActivity() {
    lateinit var attributionContext: Context

    override fun onCreate(savedInstanceState: Bundle?) {
        attributionContext = createAttributionContext("sharePhotos")
    }

    fun getLocation() {
        val locationManager = attributionContext.getSystemService(
                LocationManager::class.java) as LocationManager
        // Use "locationManager" to access device location information.
    }
}

Java

public class SharePhotoLocationActivity extends AppCompatActivity {
    private Context attributionContext;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState,
            @Nullable PersistableBundle persistentState) {
        attributionContext = createAttributionContext("sharePhotos");
    }

    public void getLocation() {
        LocationManager locationManager =
                attributionContext.getSystemService(LocationManager.class);
        if (locationManager != null) {
            // Use "locationManager" to access device location information.
        }
    }
}

הוספת תגי שיוך ליומני הגישה

מעדכנים את הקריאה החוזרת (callback) של AppOpsManager.OnOpNotedCallback כדי שהיומנים של האפליקציה יכללו את השמות של תגי השיוך שהגדרתם.

בקטע הקוד הבא מוצג הלוגיקה המעודכנת שמתעדת תגי שיוך:

Kotlin

val appOpsCallback = object : AppOpsManager.OnOpNotedCallback() {
    private fun logPrivateDataAccess(
            opCode: String, attributionTag: String, trace: String) {
        Log.i(MY_APP_TAG, "Private data accessed. " +
                    "Operation: $opCode\n " +
                    "Attribution Tag:$attributionTag\nStack Trace:\n$trace")
    }

    override fun onNoted(syncNotedAppOp: SyncNotedAppOp) {
        logPrivateDataAccess(syncNotedAppOp.op,
                syncNotedAppOp.attributionTag,
                Throwable().stackTrace.toString())
    }

    override fun onSelfNoted(syncNotedAppOp: SyncNotedAppOp) {
        logPrivateDataAccess(syncNotedAppOp.op,
                syncNotedAppOp.attributionTag,
                Throwable().stackTrace.toString())
    }

    override fun onAsyncNoted(asyncNotedAppOp: AsyncNotedAppOp) {
        logPrivateDataAccess(asyncNotedAppOp.op,
                asyncNotedAppOp.attributionTag,
                asyncNotedAppOp.message)
    }
}

Java

@Override
public void onCreate(@Nullable Bundle savedInstanceState,
        @Nullable PersistableBundle persistentState) {
    AppOpsManager.OnOpNotedCallback appOpsCallback =
            new AppOpsManager.OnOpNotedCallback() {
        private void logPrivateDataAccess(String opCode,
                String attributionTag, String trace) {
            Log.i("MY_APP_TAG", "Private data accessed. " +
                    "Operation: $opCode\n " +
                    "Attribution Tag:$attributionTag\nStack Trace:\n$trace");
        }

        @Override
        public void onNoted(@NonNull SyncNotedAppOp syncNotedAppOp) {
            logPrivateDataAccess(syncNotedAppOp.getOp(),
                    syncNotedAppOp.getAttributionTag(),
                    Arrays.toString(new Throwable().getStackTrace()));
        }

        @Override
        public void onSelfNoted(@NonNull SyncNotedAppOp syncNotedAppOp) {
            logPrivateDataAccess(syncNotedAppOp.getOp(),
                    syncNotedAppOp.getAttributionTag(),
                    Arrays.toString(new Throwable().getStackTrace()));
        }

        @Override
        public void onAsyncNoted(@NonNull AsyncNotedAppOp asyncNotedAppOp) {
            logPrivateDataAccess(asyncNotedAppOp.getOp(),
                    asyncNotedAppOp.getAttributionTag(),
                    asyncNotedAppOp.getMessage());
        }
    };

    AppOpsManager appOpsManager = getSystemService(AppOpsManager.class);
    if (appOpsManager != null) {
        appOpsManager.setOnOpNotedCallback(getMainExecutor(), appOpsCallback);
    }
}