Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ buildscript {
ext.preferenceVersion = '1.2.0'
ext.recyclerviewVersion = '1.3.2'
ext.webkitVersion = '1.10.0'
ext.workVersion = '2.7.0'

ext.slf4jVersion = '1.7.36'
ext.volleyVersion = '1.2.1'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* SPDX-FileCopyrightText: 2025 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

package com.google.android.gms.auth.api.identity;

parcelable ClearTokenRequest;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* SPDX-FileCopyrightText: 2025 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

package com.google.android.gms.auth.api.identity;

parcelable RevokeAccessRequest;
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,13 @@ import com.google.android.gms.auth.api.identity.internal.IAuthorizationCallback;
import com.google.android.gms.auth.api.identity.internal.IVerifyWithGoogleCallback;
import com.google.android.gms.auth.api.identity.AuthorizationRequest;
import com.google.android.gms.auth.api.identity.VerifyWithGoogleRequest;
import com.google.android.gms.auth.api.identity.RevokeAccessRequest;
import com.google.android.gms.auth.api.identity.ClearTokenRequest;
import com.google.android.gms.common.api.internal.IStatusCallback;

interface IAuthorizationService {
void authorize(in IAuthorizationCallback callback, in AuthorizationRequest request) = 0;
void verifyWithGoogle(in IVerifyWithGoogleCallback callback, in VerifyWithGoogleRequest request) = 1;
void revokeAccess(in IStatusCallback callback, in RevokeAccessRequest request) = 2;
void clearToken(in IStatusCallback callback, in ClearTokenRequest request) = 3;
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,13 @@ public static abstract class Builder {
public void writeToParcel(@NonNull Parcel parcel, int flags) {
CREATOR.writeToParcel(this, parcel, flags);
}

@Override
public String toString() {
return "RevokeAccessRequest{" +
"scopes=" + scopes +
", account=" + account +
", sessionId='" + sessionId + '\'' +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* SPDX-FileCopyrightText: 2025 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

package org.microg.gms.common

import android.accounts.Account
import android.annotation.SuppressLint
import android.content.Context
import android.content.Context.MODE_PRIVATE
import androidx.core.content.edit
import org.microg.gms.auth.AuthConstants

class AccountUtils(val context: Context) {

private val prefs = context.getSharedPreferences("common.selected_account_prefs", MODE_PRIVATE)

companion object {
private const val TYPE = "selected_account_type:"
@SuppressLint("StaticFieldLeak")
@Volatile
private var instance: AccountUtils? = null
fun get(context: Context): AccountUtils = instance ?: synchronized(this) {
instance ?: AccountUtils(context.applicationContext).also { instance = it }
}
}

fun saveSelectedAccount(packageName: String, account: Account?) {
if (account != null) {
prefs.edit {
putString(packageName, account.name)
putString(TYPE.plus(packageName), account.type)
}
}
}

fun getSelectedAccount(packageName: String): Account? {
val name = prefs.getString(packageName, null) ?: return null
val type = prefs.getString(TYPE.plus(packageName), AuthConstants.DEFAULT_ACCOUNT_TYPE) ?: return null
return Account(name, type)
}

fun removeSelectedAccount(packageName: String) {
prefs.edit {
remove(packageName)
remove(TYPE.plus(packageName))
}
}
}
2 changes: 2 additions & 0 deletions play-services-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ dependencies {

implementation "androidx.lifecycle:lifecycle-service:$lifecycleVersion"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"

implementation "androidx.work:work-runtime-ktx:$workVersion"
}

android {
Expand Down
3 changes: 1 addition & 2 deletions play-services-core/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -412,8 +412,7 @@
</receiver>

<receiver
android:name="org.microg.gms.gcm.UnregisterReceiver"
android:process=":persistent">
android:name="org.microg.gms.common.PersistentTrustedReceiver">
<intent-filter>
<action android:name="android.intent.action.PACKAGE_DATA_CLEARED" />
<action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package org.microg.gms.auth.credentials.identity

import android.accounts.AccountManager
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_IMMUTABLE
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
Expand All @@ -16,6 +17,8 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.google.android.gms.auth.api.identity.AuthorizationRequest
import com.google.android.gms.auth.api.identity.AuthorizationResult
import com.google.android.gms.auth.api.identity.ClearTokenRequest
import com.google.android.gms.auth.api.identity.RevokeAccessRequest
import com.google.android.gms.auth.api.identity.VerifyWithGoogleRequest
import com.google.android.gms.auth.api.identity.VerifyWithGoogleResult
import com.google.android.gms.auth.api.identity.internal.IAuthorizationCallback
Expand All @@ -26,21 +29,26 @@ import com.google.android.gms.auth.api.signin.internal.SignInConfiguration
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.api.Scope
import com.google.android.gms.common.api.Status
import com.google.android.gms.common.api.internal.IStatusCallback
import com.google.android.gms.common.internal.ConnectionInfo
import com.google.android.gms.common.internal.GetServiceRequest
import com.google.android.gms.common.internal.IGmsCallbacks
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.microg.gms.BaseService
import org.microg.gms.auth.AuthConstants
import org.microg.gms.auth.credentials.FEATURES
import org.microg.gms.auth.signin.AuthSignInActivity
import org.microg.gms.auth.signin.SignInConfigurationService
import org.microg.gms.auth.signin.getOAuthManager
import org.microg.gms.auth.signin.getServerAuthTokenManager
import org.microg.gms.auth.signin.performSignIn
import org.microg.gms.auth.signin.scopeUris
import org.microg.gms.common.AccountUtils
import org.microg.gms.common.Constants
import org.microg.gms.common.GmsService
import org.microg.gms.common.PackageUtils
import java.util.concurrent.atomic.AtomicInteger

private const val TAG = "AuthorizationService"

Expand All @@ -60,42 +68,67 @@ class AuthorizationService : BaseService(TAG, GmsService.AUTHORIZATION) {

class AuthorizationServiceImpl(val context: Context, val packageName: String, override val lifecycle: Lifecycle) : IAuthorizationService.Stub(), LifecycleOwner {

companion object{
private val nextRequestCode = AtomicInteger(0)
}

override fun authorize(callback: IAuthorizationCallback?, request: AuthorizationRequest?) {
Log.d(TAG, "Method: authorize called, request:$request")
Log.d(TAG, "Method: authorize called, packageName:$packageName request:$request")
lifecycleScope.launchWhenStarted {
val account = request?.account ?: SignInConfigurationService.getDefaultAccount(context, packageName)
if (account == null) {
Log.d(TAG, "Method: authorize called, but account is null")
callback?.onAuthorized(Status.CANCELED, null)
return@launchWhenStarted
}
val requestAccount = request?.account
val account = requestAccount ?: AccountUtils.get(context).getSelectedAccount(packageName)
val googleSignInOptions = GoogleSignInOptions.Builder().apply {
setAccountName(account.name)
request?.requestedScopes?.forEach { requestScopes(it) }
if (request?.idTokenRequested == true && request.serverClientId != null) requestIdToken(request.serverClientId)
if (request?.idTokenRequested == true && request.serverClientId != null) {
if (account?.name != requestAccount?.name) {
requestEmail().requestProfile()
}
requestIdToken(request.serverClientId)
}
if (request?.serverAuthCodeRequested == true && request.serverClientId != null) requestServerAuthCode(request.serverClientId, request.forceCodeForRefreshToken)
}.build()
val intent = Intent(context, AuthSignInActivity::class.java).apply {
`package` = Constants.GMS_PACKAGE_NAME
putExtra("config", SignInConfiguration(packageName, googleSignInOptions))
}
val signInAccount = performSignIn(context, packageName, googleSignInOptions, account, false)
callback?.onAuthorized(Status.SUCCESS,
Log.d(TAG, "authorize: account: ${account?.name}")
val result = if (account != null) {
val (accessToken, signInAccount) = performSignIn(context, packageName, googleSignInOptions, account, false)
if (requestAccount != null) {
AccountUtils.get(context).saveSelectedAccount(packageName, requestAccount)
}
AuthorizationResult(
signInAccount?.serverAuthCode,
signInAccount?.idToken,
accessToken,
signInAccount?.idToken,
signInAccount?.grantedScopes?.toList().orEmpty().map { it.scopeUri },
signInAccount,
PendingIntent.getActivity(context, account.hashCode(), intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
).also { Log.d(TAG, "authorize: result:$it") })
null
)
} else {
val options = GoogleSignInOptions.Builder(googleSignInOptions).apply {
val defaultAccount = SignInConfigurationService.getDefaultAccount(context, packageName)
defaultAccount?.name?.let { setAccountName(it) }
}.build()
val intent = Intent(context, AuthSignInActivity::class.java).apply {
`package` = Constants.GMS_PACKAGE_NAME
putExtra("config", SignInConfiguration(packageName, options))
}
AuthorizationResult(
null,
null,
null,
request?.requestedScopes.orEmpty().map { it.scopeUri },
null,
PendingIntent.getActivity(context, nextRequestCode.incrementAndGet(), intent, FLAG_UPDATE_CURRENT or FLAG_IMMUTABLE)
)
}
runCatching {
callback?.onAuthorized(Status.SUCCESS, result.also { Log.d(TAG, "authorize: result:$it") })
}
}
}

override fun verifyWithGoogle(callback: IVerifyWithGoogleCallback?, request: VerifyWithGoogleRequest?) {
Log.d(TAG, "unimplemented Method: verifyWithGoogle: request:$request")
lifecycleScope.launchWhenStarted {
val account = SignInConfigurationService.getDefaultAccount(context, packageName)
val account = AccountUtils.get(context).getSelectedAccount(packageName) ?: SignInConfigurationService.getDefaultAccount(context, packageName)
if (account == null) {
Log.d(TAG, "Method: authorize called, but account is null")
callback?.onVerifed(Status.CANCELED, null)
Expand All @@ -119,4 +152,31 @@ class AuthorizationServiceImpl(val context: Context, val packageName: String, ov
}
}

override fun revokeAccess(callback: IStatusCallback?, request: RevokeAccessRequest?) {
Log.d(TAG, "Method: revokeAccess called, request:$request")
lifecycleScope.launchWhenStarted {
val authOptions = SignInConfigurationService.getAuthOptions(context, packageName)
val authAccount = request?.account
if (authOptions.isNotEmpty() && authAccount != null) {
val authManager = getOAuthManager(context, packageName, authOptions.first(), authAccount)
val token = authManager.peekAuthToken()
if (token != null) {
// todo "https://oauth2.googleapis.com/revoke"
authManager.invalidateAuthToken(token)
authManager.isPermitted = false
}
}
AccountUtils.get(context).removeSelectedAccount(packageName)
runCatching { callback?.onResult(Status.SUCCESS) }
}
}

override fun clearToken(callback: IStatusCallback?, request: ClearTokenRequest?) {
Log.d(TAG, "Method: clearToken called, request:$request")
request?.token?.let {
AccountManager.get(context).invalidateAuthToken(AuthConstants.DEFAULT_ACCOUNT_TYPE, it)
}
runCatching { callback?.onResult(Status.SUCCESS) }
}

}
Loading
Loading