Auditer l'accès aux données

Vous pouvez obtenir des informations sur la manière dont votre application et ses dépendances accèdent aux données privées des utilisateurs en effectuant un audit des accès aux données. Ce processus, disponible sur les appareils équipés d'Android 11 (niveau d'API 30) ou version ultérieure, vous permet de mieux identifier les éventuels accès non prévus à des données. Votre application peut enregistrer une instance de AppOpsManager.OnOpNotedCallback, qui peut effectuer des actions chaque fois que l'un des événements suivants se produit :

  • Le code de votre application accède à des données privées. Pour vous aider à déterminer la partie logique de votre application qui a appelé l'événement, vous pouvez auditer l'accès aux données par balise d'attribution.
  • Le code d'une bibliothèque ou d'un SDK dépendants accède à des données privées.

L'audit des accès aux données est appelé sur le thread où la requête de données a lieu. Cela signifie que si un SDK ou une bibliothèque tiers dans votre application appelle une API qui accède à des données privées, l'audit des accès aux données permet à votre OnOpNotedCallback d'examiner les informations concernant l'appel. Généralement, cet objet de rappel peut savoir si l'appel provient de votre application ou du SDK en consultant l'état actuel de l'application, comme la trace de la pile du thread actuel.

Journaliser l'accès aux données

Pour effectuer un audit des accès aux données à l'aide d'une instance de AppOpsManager.OnOpNotedCallback, implémentez la logique de rappel dans le composant où vous souhaitez auditer l'accès aux données, par exemple dans la méthode onCreate() de l'activité ou dans la méthode onCreate() d'une application.

L'extrait de code suivant définit un AppOpsManager.OnOpNotedCallback pour auditer l'accès aux données au sein d'une seule activité :

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);
    }
}

Les méthodes onAsyncNoted() et onSelfNoted() sont appelées dans des situations spécifiques :

  • onAsyncNoted() est appelé si l'accès aux données n'a pas lieu pendant l'appel d'API de votre application. L'exemple le plus courant est lorsque votre application enregistre un écouteur et que l'accès aux données se produit chaque fois que le rappel de l'écouteur est appelé.

    L'argument AsyncNotedOp transmis à onAsyncNoted() contient une méthode appelée getMessage(). Cette méthode fournit davantage d'informations sur l'accès aux données. Dans le cas des rappels d'emplacement, le message contient le hachage d'identité système de l'écouteur.

  • onSelfNoted() est appelé dans les rares cas où une application transmet son propre UID à noteOp().

Auditer l'accès aux données par balise d'attribution

Votre application peut présenter plusieurs cas d'utilisation principaux, par exemple permettre aux utilisateurs de prendre des photos et de les partager avec leurs contacts. Si vous développez une application polyvalente, vous pouvez appliquer une balise d'attribution à chaque partie de l'application lorsque vous auditez l'accès aux données. Le contexte attributionTag est renvoyé dans les objets transmis aux appels à onNoted(). Cela vous permet de retracer plus facilement l'accès aux données vers des parties logiques de votre code.

Pour définir des balises d'attribution dans votre application, procédez comme indiqué dans les sections suivantes.

Déclarer des balises d'attribution dans le fichier manifeste

Si votre application cible Android 12 (niveau d'API 31) ou une version ultérieure, vous devez déclarer les balises d'attribution dans le fichier manifeste de votre application, en utilisant le format indiqué dans l'extrait de code suivant. Si vous tentez d'utiliser une balise d'attribution que vous ne déclarez pas dans le fichier manifeste de votre application, le système crée une balise null et consigne un message dans 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>

Créer des balises d'attribution

Dans la méthode onCreate() de l'activité où vous accédez aux données, telle que l'activité pour laquelle vous demandez la position ou accédez à la liste des contacts de l'utilisateur, appelez createAttributionContext() en transmettant la balise d'attribution que vous souhaitez associer à une partie de votre application.

L'extrait de code suivant montre comment créer une balise d'attribution pour la partie "partage de position des photos" d'une application :

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

Inclure des balises d'attribution dans les journaux d'accès

Mettez à jour votre rappel AppOpsManager.OnOpNotedCallback afin que les journaux de votre application incluent les noms des balises d'attribution que vous avez définies.

L'extrait de code suivant montre une nouvelle logique qui consigne les balises d'attribution :

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);
    }
}