Skip to content

Commit

Permalink
Automatic account error diagnosis
Browse files Browse the repository at this point in the history
Logs which of the dependencies for setting up enterprise accounts are missing:

* Checkin enabled
* GCM enabled
* microG allowed to use GCM
* Lockscreen enabled
  • Loading branch information
fynngodau committed Apr 6, 2024
1 parent 750ce40 commit 77f2a6e
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
import android.net.Uri;
import android.util.Log;

import androidx.annotation.RequiresPermission;

import org.microg.gms.accountaction.ErrorResolverKt;
import org.microg.gms.accountaction.Resolution;
import org.microg.gms.common.NotOkayException;
Expand Down Expand Up @@ -256,7 +254,7 @@ public AuthResponse requestAuthWithBackgroundResolution(boolean legacy) throws I
return requestAuth(legacy);
} catch (NotOkayException e) {
if (e.getMessage() != null) {
Resolution errorResolution = ErrorResolverKt.fromErrorMessage(e.getMessage());
Resolution errorResolution = ErrorResolverKt.resolveAuthErrorMessage(context, e.getMessage());
if (errorResolution != null) {
return ErrorResolverKt.initiateFromBackgroundBlocking(
errorResolution,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,115 @@ package org.microg.gms.accountaction

import android.accounts.Account
import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import android.util.Log
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.microg.gms.common.Constants
import org.microg.gms.cryptauth.isLockscreenConfigured
import org.microg.gms.cryptauth.syncCryptAuthKeys
import org.microg.gms.gcm.GcmDatabase
import org.microg.gms.gcm.GcmPrefs
import org.microg.gms.settings.SettingsContract
import java.io.IOException


/**
* High-level resolution: tell server that user has configured a lock screen
*/
const val DEVICE_MANAGEMENT_SCREENLOCK_REQUIRED = "DeviceManagementScreenlockRequired"

/**
* Indicates that the user is using an enterprise account that is set up to use Advanced
* device management features, for which it is required to install a device manager.
* This is not supported by microG.
*/
const val DEVICE_MANAGEMENT_REQUIRED = "DeviceManagementRequired"

/**
* Indicates that the user is using an enterprise account that is set up to use Advanced
* device management features, for which it is required to install a device manager,
* and that the device also needs manual admin approval.
* This is not supported by microG.
*/
const val DEVICE_MANAGEMENT_ADMIN_PENDING_APPROVAL = "DeviceManagementAdminPendingApproval"

const val TAG = "GmsAccountErrorResolve"

/**
* @return `null` if it is unknown how to resolve the problem, an
* appropriate `Resolution` otherwise
*/
fun fromErrorMessage(s: String): Resolution? = if (s.startsWith("Error=")) {
fromErrorMessage(s.drop("Error=".length))
fun Context.resolveAuthErrorMessage(s: String): Resolution? = if (s.startsWith("Error=")) {
resolveAuthErrorMessage(s.drop("Error=".length))
} else when (s) {
"DeviceManagementScreenlockRequired" -> {
if (true) {
DEVICE_MANAGEMENT_SCREENLOCK_REQUIRED -> {

val actions = mutableSetOf<UserAction>()



val settingsProjection = arrayOf(
SettingsContract.CheckIn.ENABLED,
SettingsContract.CheckIn.LAST_CHECK_IN
)
SettingsContract.getSettings(this, SettingsContract.CheckIn.getContentUri(this), settingsProjection) { cursor ->
val checkInEnabled = cursor.getInt(0) != 0
val lastCheckIn = cursor.getLong(1)

if (lastCheckIn <= 0 && !checkInEnabled) {
actions += UserAction.ENABLE_CHECKIN
}
}

val gcmPrefs = GcmPrefs.get(this)
if (!gcmPrefs.isEnabled) {
actions += UserAction.ENABLE_GCM
}

val gcmDatabaseEntry = GcmDatabase(this).use {
it.getApp(Constants.GMS_PACKAGE_NAME)
}
if (gcmDatabaseEntry != null &&
!gcmDatabaseEntry.allowRegister ||
gcmDatabaseEntry == null &&
gcmPrefs.confirmNewApps
) {
actions += UserAction.ALLOW_MICROG_GCM
}

if (!isLockscreenConfigured()) {
actions += UserAction.ENABLE_LOCKSCREEN
}

if (actions.isEmpty()) {
CryptAuthSyncKeys
} else {
UserIntervention(
setOf(
UserAction.ENABLE_CHECKIN, UserAction.ENABLE_GCM, UserAction.ENABLE_LOCKSCREEN
)
)
UserIntervention(actions)
}
}

"DeviceManagementRequired" -> NoResolution
"DeviceManagementAdminPendingApproval" -> NoResolution
DEVICE_MANAGEMENT_ADMIN_PENDING_APPROVAL, DEVICE_MANAGEMENT_REQUIRED ->
NoResolution(NoResolutionReason.ADVANCED_DEVICE_MANAGEMENT_NOT_SUPPORTED)
else -> null
}
}.also { Log.d(TAG, "Error was: $s. Diagnosis: $it.") }


fun <T> Resolution.initiateFromBackgroundBlocking(context: Context, account: Account, retryFunction: RetryFunction<T>): T? {
when (this) {
CryptAuthSyncKeys -> {
Log.d(TAG, "Resolving account error by performing cryptauth sync keys call.")
runBlocking {
syncCryptAuthKeys(context, account)
}
return retryFunction.run()
}
NoResolution -> TODO()
is UserIntervention -> TODO()
is NoResolution -> {
Log.w(TAG, "This account cannot be used with microG due to $reason")
return null
}
is UserIntervention -> {
Log.w(TAG, "User intervention required! You need to ${actions.joinToString(", ")}.")
return null
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,20 @@ data object CryptAuthSyncKeys : Resolution()
*/
data class UserIntervention(val actions: Set<UserAction>) : Resolution()

/**
* Represents a situation that is known to be unsupported by microG.
* Advise the user to remove the account.
*/
data object NoResolution : Resolution()

enum class UserAction {
ENABLE_CHECKIN,
ENABLE_GCM,
ALLOW_MICROG_GCM,
ENABLE_LOCKSCREEN,
REAUTHENTICATE
}

/**
* Represents a situation that is known to be unsupported by microG.
* Advise the user to remove the account.
*/
data class NoResolution(val reason: NoResolutionReason) : Resolution()

enum class NoResolutionReason {
ADVANCED_DEVICE_MANAGEMENT_NOT_SUPPORTED
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.microg.gms.cryptauth

import android.accounts.Account
import android.app.KeyguardManager
import android.content.Context
import android.util.Log
import com.google.android.gms.BuildConfig
Expand Down Expand Up @@ -57,7 +58,7 @@ suspend fun syncCryptAuthKeys(context: Context, accountName: String): JSONObject
.extraParam("scope", GCM_REGISTER_SCOPE)
).let {
if (!it.containsKey(GcmConstants.EXTRA_REGISTRATION_ID)) {
Log.d(TAG, "No instance ID was gathered. Is GCM enabled?")
Log.d(TAG, "No instance ID was gathered. Is GCM enabled, has there been a checkin?")
return null
}
it.getString(GcmConstants.EXTRA_REGISTRATION_ID)!!
Expand Down Expand Up @@ -92,7 +93,7 @@ suspend fun syncCryptAuthKeys(context: Context, accountName: String): JSONObject
device_model = Build.MODEL ?: "",
device_manufacturer = Build.MANUFACTURER ?: "",
device_type = ClientAppMetadata.DeviceType.ANDROID,
using_secure_screenlock = true, // TODO actual value
using_secure_screenlock = context.isLockscreenConfigured(),
bluetooth_radio_supported = true, // TODO actual value? doesn't seem relevant
// bluetooth_radio_enabled = false,
ble_radio_supported = true, // TODO: actual value? doesn't seem relevant
Expand Down Expand Up @@ -161,5 +162,14 @@ suspend fun syncCryptAuthKeys(context: Context, accountName: String): JSONObject
}
}

fun Context.isLockscreenConfigured(): Boolean {
val service: KeyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
return service.isDeviceSecure
} else {
return service.isKeyguardSecure
}
}

fun <K, V> jsonObjectOf(vararg pairs: Pair<K, V>): JSONObject = JSONObject(mapOf(*pairs))
inline fun <reified T> jsonArrayOf(vararg elements: T): JSONArray = JSONArray(arrayOf(*elements))

0 comments on commit 77f2a6e

Please sign in to comment.