نقل بيانات الاعتماد

تتيح واجهات برمجة التطبيقات لنقل بيانات الاعتماد في "إدارة بيانات الاعتماد" نقل بيانات اعتماد المستخدمين بشكل آمن على الجهاز نفسه بين مقدّمي بيانات الاعتماد. يوضّح هذا الدليل بالتفصيل كيف يمكن لمقدّمي بيانات الاعتماد على Android الدمج مع واجهات برمجة التطبيقات التي توفّرها androidx.credentials:providerevents مكتبة. تتيح هذه الميزة استخدام كلمات المرور ومفاتيح المرور ومعلومات العنوان والحقول المخصّصة باستخدام "تنسيق تبادل بيانات الاعتماد" (CXF) الموحّد من FIDO.

المفاهيم الأساسية

يسهّل إطار عمل نقل بيانات الاعتماد نقل بيانات الاعتماد من نظير إلى نظير على الجهاز نفسه بدون عرض بيانات الاعتماد الأولية على نظام التشغيل Android أو التطبيقات غير المصادَق عليها.

يحدّد إطار العمل دورَين أساسيَين:

  • المصدِّر (مقدّم المصدر): هو مقدّم بيانات اعتماد يحمل حاليًا بيانات اعتماد المستخدم. ويسجّل مسبقًا البيانات الوصفية حول الحسابات القابلة للتصدير المتاحة (ExportEntry) لدى النظام ويستجيب لطلبات النقل عندما يختارها المستخدم.
  • المستورِد (مقدّم العميل أو معالج الإعداد): هو مقدّم بيانات اعتماد أو معالج إعداد يبدأ طلب استيراد (ImportCredentialsRequest) يحدّد أنواع بيانات الاعتماد و الإضافات التي يمكنه تلقّيها.

التوافق مع إصدار Android

تعمل واجهة برمجة التطبيقات لنقل بيانات الاعتماد على الأجهزة التي تعمل بالإصدار 8 من نظام التشغيل Android (المستوى 26 من واجهة برمجة التطبيقات) والإصدارات الأحدث.

إضافة التبعيات

أضِف التبعية androidx.credentials:providerevents إلى build.gradle أو build.gradle.kts في الوحدة:

dependencies {
    implementation("androidx.credentials:providerevents:1.0.0-alpha06")
}

إنشاء مثيل للفئة المطلوبة

أنشئ مثيلاً من المطلوب ProviderEventsManager.

val providerEventsManager = ProviderEventsManager.create(context)

تنفيذ دور المصدِّر

للسماح للمستخدمين بتصدير بيانات الاعتماد من تطبيقك إلى مقدّمي بيانات اعتماد آخرين على الجهاز، نفِّذ دور المصدِّر.

تسجيل إدخالات التصدير

عندما يتغيّر مقدّم بيانات الاعتماد (على سبيل المثال، عندما يسجّل المستخدم الدخول أو يضيف بيانات اعتماد أو يعدّل الحسابات)، سجِّل عناصر ExportEntry أو عدِّلها باستخدام ProviderEventsManager.registerExport().

يتطلّب كل ExportEntry ما يلي:

  • id: هو معرّف سلسلة سرية يتم إنشاؤها عشوائيًا ويمثّل بشكل فريد إدخال التصدير هذا. عليك الاحتفاظ بهذا المعرّف بشكل آمن، لأنّك ستحتاج إليه لاحقًا للتحقّق من طلبات النقل الواردة.
  • accountDisplayName: هو تصنيف حساب اختياري (على سبيل المثال، "Personal Account").
  • userDisplayName: هو المعرّف الأساسي للمستخدم (على سبيل المثال، "alice@example.com").
  • icon: هو رمز Bitmap يمثّل مقدّم الخدمة أو الحساب (تعدّله المكتبة تلقائيًا إلى صورة PNG بدقة 32×32).
  • supportedCredentialTypes: هي مجموعة من الثوابت النصية من CredentialTypes تمثّل أنواع بيانات الاعتماد التي يحتوي عليها هذا الإدخال.

suspend fun registerMyProviderForExport(
    providerEventsManager: ProviderEventsManager,
    providerIcon: Bitmap,
    // Randomly generated and stored in encrypted storage
    secretEntryId: String
) {
    val entry = ExportEntry(
        id = secretEntryId,
        accountDisplayName = "MyProvider Personal",
        userDisplayName = "alice@example.com",
        icon = providerIcon,
        supportedCredentialTypes = setOf(
            CredentialTypes.CREDENTIAL_TYPE_BASIC_AUTH, // Passwords
            CredentialTypes.CREDENTIAL_TYPE_PUBLIC_KEY, // Passkeys
            CredentialTypes.CREDENTIAL_TYPE_ADDRESS,
            CredentialTypes.CREDENTIAL_TYPE_CREDIT_CARD
        )
    )

    // RegisterExportRequest.create() attaches the default WASM matcher from assets
    val request = RegisterExportRequest.create(context, listOf(entry))

    try {
        val response = providerEventsManager.registerExport(request)
        // Registration successful
    } catch (e: Exception) {
        // Handle registration exceptions (e.g., RegisterExportProviderConfigurationException)
    }
}

الإعلان عن نشاط المصدِّر في ملف البيان

عندما يختار المستخدم ExportEntry في واجهة مستخدم أداة الاختيار في النظام، يطلق النظام Activity المعيّن لمعالجة الطلب. أعلِن عن هذا النشاط باستخدام إجراء النية ومعرّف الموارد المنتظم (URI) للمحتوى الإلزاميَين:

<activity
    android:name="com.example.CredentialExportActivity"
    android:exported="true"
    android:label="@string/export_activity_label">
    <intent-filter>
        // This intent action is required for Credential Manager to invoke this activity
        <action android:name="androidx.identitycredentials.action.IMPORT_CREDENTIALS" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:scheme="content" />
    </intent-filter>
</activity>

معالجة غرض النقل في نشاطك

في CredentialExportActivity، استخدِم IntentHandler.retrieveProviderImportCredentialsRequest(intent) لتحليل الطلب والتحقّق من التطبيق الذي استدعى الطلب وcredId وإجراء أي مصادقة مطلوبة باستخدام المقاييس الحيوية وكتابة حمولة FIDO CXF مرة أخرى إلى معرّف موارد منتظم للمحتوى المقدَّم:

class CredentialExportActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 1. Extract the transfer request from the incoming Intent
        val request: ProviderImportCredentialsRequest? =
            IntentHandler.retrieveProviderImportCredentialsRequest(intent)

        if (request == null) {
            finishWithError()
            return
        }

        // 2. Validate CallingAppInfo and secret `credId`
        val callingAppPackage = request.callingAppInfo.packageName
        val receivedCredId = request.credId
        if (!verifySecretEntryId(receivedCredId) || !isTrustedImporter(callingAppPackage)) {
            // Secret ID mismatch or untrusted caller -> abort
            sendExceptionAndFinish(ImportCredentialsNoExportOptionException("Unauthorized request"))
            return
        }

        // 3. Optional: Prompt user for Biometric / PIN authentication before exporting
        authenticateUserThenExport(request)
    }

    private fun authenticateUserThenExport(request: ProviderImportCredentialsRequest) {
        // ... Biometric prompt logic ...
        // Once authenticated, generate the FIDO CXF JSON string matching the requested types
        val cxfJsonPayload = buildFidoCxfJsonPayload(
            requestedTypes = request.request.credentialTypes,
            requestedExtensions = request.request.knownExtensions
        )

        val response = ImportCredentialsResponse(cxfJsonPayload)

        // 4. Write the JSON payload to the Content URI and set Activity result
        IntentHandler.setImportCredentialsResponse(
            context = this,
            uri = request.uri,
            intent = intent,
            response = response
        )
        setResult(Activity.RESULT_OK, intent)
        finish()
    }

    private fun sendExceptionAndFinish(exception: androidx.credentials.providerevents.exception.ImportCredentialsException) {
        IntentHandler.setImportCredentialsException(intent, exception)
        setResult(Activity.RESULT_OK, intent)
        finish()
    }

    private fun verifySecretEntryId(credentialId: String): Boolean {
        // Check if credentialId matches what you stored when calling RegisterExportRequest
        return credentialId == getStoredSecretEntryId()
    }

    private fun isTrustedImporter(packageName: String): Boolean {
        // Implement any specific allowlisting / caller checks if required
        return true
    }

    private fun finishWithError() {
        setResult(Activity.RESULT_CANCELED)
        finish()
    }
}

استثناءات تسجيل التصدير ومحوه

جميع الاستثناءات في هذه الوحدة هي فئات فرعية من ImportCredentialsException، RegisterExportException، أو ClearExportException.

تنفيذ دور المستورِد

لاستيراد بيانات الاعتماد إلى تطبيقك (على سبيل المثال، أثناء الإعداد أو استيراد مقدّم الخدمة )، نفِّذ دور المستورِد وابدأ عملية الاستيراد من خلال استدعاء ProviderEventsManager.importCredentials().

إنشاء طلب الاستيراد وبدء العملية

حدِّد CredentialTypes وKnownExtensions التي يتيحها المستورِد:

suspend fun startCredentialImport(
    activityContext: Context,
    providerEventsManager: ProviderEventsManager
) {
    val importRequest = ImportCredentialsRequest(
        credentialTypes = setOf(
            CredentialTypes.CREDENTIAL_TYPE_BASIC_AUTH,
            CredentialTypes.CREDENTIAL_TYPE_PUBLIC_KEY,
            CredentialTypes.CREDENTIAL_TYPE_ADDRESS,
            CredentialTypes.CREDENTIAL_TYPE_NOTE
        ),
        knownExtensions = setOf(
            KnownExtensions.KNOWN_EXTENSION_SHARED
        )
    )

    try {
        // Launches the system Selector UI; suspends until user selects a provider and completes transfer
        val response = providerEventsManager.importCredentials(activityContext, importRequest)

        // 1. Inspect the source exporter's package info
        val exporterPackageName = response.callingAppInfo.packageName

        // 2. Parse the FIDO CXF JSON string
        val cxfJsonString = response.response.responseJson
        parseAndSaveImportedCredentials(cxfJsonString)
    } catch (e: ImportCredentialsException) {
        // Handle specific import exceptions (e.g., ImportCredentialsCancellationException)
        handleImportFailure(e)
    }
}

private fun parseAndSaveImportedCredentials(cxfJsonString: String) {
    val rootJson = JSONObject(cxfJsonString)
    // Parse according to FIDO Credential Exchange Format (CXF v1.0) specification:
    // https://fidoalliance.org/specs/cx/cxf-v1.0-ps-20250814.html
}

// Helper function to make it compile
private fun handleImportFailure(e: ImportCredentialsException) {}

استثناءات عملية الاستيراد

جميع الاستثناءات في هذه الوحدة هي فئات فرعية من ImportCredentialsException، RegisterExportException، أو ClearExportException.

أنواع بيانات الاعتماد والإضافات المتوافقة

يحدّد العنصر androidx.credentials.providerevents.transfer.CredentialTypes ثوابت نصية موحّدة تتوافق مع أنواع عناصر FIDO CXF:

ثابت القيمة (نوع cxf) الوصف
CREDENTIAL_TYPE_BASIC_AUTH "basic-auth" بيانات اعتماد تسجيل الدخول باستخدام اسم المستخدم وكلمة المرور
CREDENTIAL_TYPE_PUBLIC_KEY "passkey" بيانات اعتماد المفتاح العام لمفتاح المرور FIDO2 أو WebAuthn
CREDENTIAL_TYPE_ADDRESS "address" معلومات عنوان الشحن أو العنوان البريدي للتعبئة التلقائية للنماذج
CREDENTIAL_TYPE_API_KEY "api-key" رموز الوصول إلى واجهة برمجة التطبيقات والرموز المميّزة
CREDENTIAL_TYPE_CREDIT_CARD "credit-card" معلومات الدفع باستخدام بطاقات الائتمان وبطاقات السحب الآلي
CREDENTIAL_TYPE_CUSTOM_FIELDS "custom-fields" التجميعات المخصّصة أو الحقول التي يحدّدها المستخدم
CREDENTIAL_TYPE_DRIVERS_LICENSE "drivers-license" تفاصيل رخصة القيادة
CREDENTIAL_TYPE_FILE "file" البيانات الوصفية والمراجع النائبة للملفات الثنائية
CREDENTIAL_TYPE_GENERATED_PASSWORD "generated-password" كلمات المرور الآمنة التي يتم إنشاؤها آليًا
CREDENTIAL_TYPE_IDENTITY_DOCUMENT "identity-document" مراجع جوازات السفر أو بطاقات التعريف الوطنية أو أرقام الضمان الاجتماعي أو أرقام تعريف دافعي الضرائب
CREDENTIAL_TYPE_ITEM_REFERENCE "item-reference" الروابط المنطقية التي تشير إلى عنصر آخر في الحمولة
CREDENTIAL_TYPE_NOTE "note" الملاحظات الآمنة التي يحدّدها المستخدم (سلسلة UTF-8)
CREDENTIAL_TYPE_PASSPORT "passport" تفاصيل وثيقة السفر في جواز السفر
CREDENTIAL_TYPE_PERSON_NAME "person-name" تفاصيل هوية الشخص واسمه
CREDENTIAL_TYPE_SSH_KEY "ssh-key" أزواج المفاتيح العامة والخاصة لبروتوكول SSH
CREDENTIAL_TYPE_TOTP "totp" أسرار كلمة المرور الصالحة لمرة واحدة المستندة إلى الوقت (المصادقة الثنائية)
CREDENTIAL_TYPE_WIFI "wifi" معرّف SSID وعبارات المرور لشبكة Wi-Fi

أدوات المطابقة المخصّصة لـ WASM (WebAssembly) (ميزة متقدّمة)

تلقائيًا، يؤدي استدعاء RegisterExportRequest.create(context, entries) إلى تجميع credential_transfer_matcher.wasm التلقائي للنظام من مواد عرض المكتبة، ما يؤدي إلى فلترة الإدخالات استنادًا إلى تقاطع supportedCredentialTypes فقط.

إذا كان مقدّم بيانات الاعتماد يتطلّب منطق مطابقة معقدًا (على سبيل المثال، عمليات التحقّق من الإمكانات الديناميكية أو الفلترة الشرطية استنادًا إلى الحقول المخصّصة)، يمكنك تجميع وحدة WebAssembly الخاصة بك التي تتطابق مع واجهة برمجة تطبيقات نقل بيانات اعتماد WASM وتمرير مصفوفة البايت الأولية مباشرةً:

// Loading a custom WASM matcher
val customMatcherBytes = context.assets.open("my_custom_matcher.wasm").use { it.readBytes() }
val customRequest = RegisterExportRequest(
    entries = myEntries,
    exportMatcher = customMatcherBytes
)
providerEventsManager.registerExport(customRequest)