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

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

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

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

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

כדי לבצע ביקורת על גישה לנתונים באמצעות מופע של AppOpsManager.OnOpNotedCallback, מטמיעים את הלוגיקה של קריאה חוזרת (callback) ברכיב שבו רוצים לבצע ביקורת על גישה לנתונים, למשל ב-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() מכיל שיטה בשם getMessage(). השיטה הזו מספקת מידע נוסף על הגישה לנתונים. במקרה של קריאות חזרה למיקום, ההודעה מכילה את גיבוב הזהות של המערכת של המאזין.

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

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

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

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

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

אם האפליקציה שלכם מטרגטת ל-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>

יצירת תגי שיוך

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

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

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.
        }
    }
}

הכללת תגי שיוך ביומני הגישה

מעדכנים את הפונקציה החוזרת 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.setNotedAppOpsCollector(appOpsCollector);
    }
}