76 lines
3.0 KiB
Kotlin
76 lines
3.0 KiB
Kotlin
// Android cross-app SSO scaffold.
|
|
//
|
|
// All suite apps are signed with the SAME key, so a ContentProvider guarded by
|
|
// a signature-level permission lets them share the OIDC session that ulti-core
|
|
// persists. One app (e.g. UltiMail) hosts the provider; siblings read/write
|
|
// through it. Backed by EncryptedSharedPreferences (androidx.security:security-crypto).
|
|
//
|
|
// Alternative: a custom AccountManager account type ("space.ulti.suite") with a
|
|
// shared authenticator — heavier, but integrates with system account settings.
|
|
//
|
|
// Manifest (host app), plus a matching <uses-permission> in every sibling:
|
|
// <permission android:name="space.ulti.suite.permission.SESSION"
|
|
// android:protectionLevel="signature" />
|
|
// <provider
|
|
// android:name=".sso.SharedSessionProvider"
|
|
// android:authorities="space.ulti.suite.session"
|
|
// android:exported="true"
|
|
// android:readPermission="space.ulti.suite.permission.SESSION"
|
|
// android:writePermission="space.ulti.suite.permission.SESSION" />
|
|
|
|
package space.ulti.suite.sso
|
|
|
|
import android.content.ContentProvider
|
|
import android.content.ContentValues
|
|
import android.content.Context
|
|
import android.database.Cursor
|
|
import android.database.MatrixCursor
|
|
import android.net.Uri
|
|
import androidx.security.crypto.EncryptedSharedPreferences
|
|
import androidx.security.crypto.MasterKey
|
|
|
|
class SharedSessionProvider : ContentProvider() {
|
|
|
|
override fun onCreate(): Boolean = true
|
|
|
|
private fun prefs(ctx: Context) = EncryptedSharedPreferences.create(
|
|
ctx,
|
|
"ulti_shared_session",
|
|
MasterKey.Builder(ctx).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(),
|
|
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
|
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
|
|
)
|
|
|
|
// query(content://space.ulti.suite.session/<key>) -> single-row "value".
|
|
override fun query(
|
|
uri: Uri, projection: Array<out String>?, selection: String?,
|
|
selectionArgs: Array<out String>?, sortOrder: String?,
|
|
): Cursor {
|
|
val key = uri.lastPathSegment ?: ""
|
|
val value = prefs(context!!).getString(key, null)
|
|
return MatrixCursor(arrayOf("value")).apply { addRow(arrayOf(value)) }
|
|
}
|
|
|
|
// insert with values{ key, value } (value=null deletes).
|
|
override fun insert(uri: Uri, values: ContentValues?): Uri? {
|
|
val key = values?.getAsString("key") ?: return null
|
|
val value = values.getAsString("value")
|
|
prefs(context!!).edit().apply {
|
|
if (value == null) remove(key) else putString(key, value)
|
|
}.apply()
|
|
return uri
|
|
}
|
|
|
|
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
|
|
val key = uri.lastPathSegment ?: return 0
|
|
prefs(context!!).edit().remove(key).apply()
|
|
return 1
|
|
}
|
|
|
|
override fun update(
|
|
uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?,
|
|
): Int = insert(uri, values)?.let { 1 } ?: 0
|
|
|
|
override fun getType(uri: Uri): String = "vnd.android.cursor.item/ulti.session"
|
|
}
|