diff --git a/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/identity/AuthorizationRequest.aidl b/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/identity/AuthorizationRequest.aidl new file mode 100644 index 0000000000..763861ec77 --- /dev/null +++ b/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/identity/AuthorizationRequest.aidl @@ -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 AuthorizationRequest; \ No newline at end of file diff --git a/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/identity/AuthorizationResult.aidl b/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/identity/AuthorizationResult.aidl new file mode 100644 index 0000000000..7c1a1d2d5b --- /dev/null +++ b/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/identity/AuthorizationResult.aidl @@ -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 AuthorizationResult; \ No newline at end of file diff --git a/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/identity/VerifyWithGoogleRequest.aidl b/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/identity/VerifyWithGoogleRequest.aidl new file mode 100644 index 0000000000..3f35542889 --- /dev/null +++ b/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/identity/VerifyWithGoogleRequest.aidl @@ -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 VerifyWithGoogleRequest; \ No newline at end of file diff --git a/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/identity/VerifyWithGoogleResult.aidl b/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/identity/VerifyWithGoogleResult.aidl new file mode 100644 index 0000000000..db54e530d6 --- /dev/null +++ b/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/identity/VerifyWithGoogleResult.aidl @@ -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 VerifyWithGoogleResult; \ No newline at end of file diff --git a/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/identity/internal/IAuthorizationCallback.aidl b/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/identity/internal/IAuthorizationCallback.aidl new file mode 100644 index 0000000000..e00719b305 --- /dev/null +++ b/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/identity/internal/IAuthorizationCallback.aidl @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2025 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.auth.api.identity.internal; + +import com.google.android.gms.common.api.Status; +import com.google.android.gms.auth.api.identity.AuthorizationResult; + +interface IAuthorizationCallback { + void onAuthorized(in Status status, in AuthorizationResult result); +} \ No newline at end of file diff --git a/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/identity/internal/IAuthorizationService.aidl b/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/identity/internal/IAuthorizationService.aidl new file mode 100644 index 0000000000..db1fc73f2a --- /dev/null +++ b/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/identity/internal/IAuthorizationService.aidl @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: 2025 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.auth.api.identity.internal; + +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; + +interface IAuthorizationService { + void authorize(in IAuthorizationCallback callback, in AuthorizationRequest request) = 0; + void verifyWithGoogle(in IVerifyWithGoogleCallback callback, in VerifyWithGoogleRequest request) = 1; +} \ No newline at end of file diff --git a/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/identity/internal/IVerifyWithGoogleCallback.aidl b/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/identity/internal/IVerifyWithGoogleCallback.aidl new file mode 100644 index 0000000000..dde349f9bd --- /dev/null +++ b/play-services-auth/src/main/aidl/com/google/android/gms/auth/api/identity/internal/IVerifyWithGoogleCallback.aidl @@ -0,0 +1,13 @@ +/* + * SPDX-FileCopyrightText: 2025 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.auth.api.identity.internal; + +import com.google.android.gms.common.api.Status; +import com.google.android.gms.auth.api.identity.VerifyWithGoogleResult; + +interface IVerifyWithGoogleCallback { + void onVerifed(in Status status, in VerifyWithGoogleResult result); +} \ No newline at end of file diff --git a/play-services-auth/src/main/java/com/google/android/gms/auth/api/identity/AuthorizationRequest.java b/play-services-auth/src/main/java/com/google/android/gms/auth/api/identity/AuthorizationRequest.java new file mode 100644 index 0000000000..f318390ce1 --- /dev/null +++ b/play-services-auth/src/main/java/com/google/android/gms/auth/api/identity/AuthorizationRequest.java @@ -0,0 +1,67 @@ +/** + * SPDX-FileCopyrightText: 2025 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.auth.api.identity; + +import android.accounts.Account; +import android.os.Bundle; +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.api.Scope; +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +import java.util.List; + +@SafeParcelable.Class +public class AuthorizationRequest extends AbstractSafeParcelable { + + @Field(1) + public List requestedScopes; + @Field(2) + public String serverClientId; + @Field(3) + public boolean serverAuthCodeRequested; + @Field(4) + public boolean idTokenRequested; + @Field(5) + public Account account; + @Field(6) + public String hostedDomainFilter; + @Field(7) + public String sessionId; + @Field(8) + public boolean forceCodeForRefreshToken; + @Field(9) + public Bundle bundle; + @Field(10) + public boolean offlineAccess; + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(AuthorizationRequest.class); + + @Override + public String toString() { + return "AuthorizationRequest{" + + "requestedScopes=" + requestedScopes + + ", serverClientId='" + serverClientId + '\'' + + ", serverAuthCodeRequested=" + serverAuthCodeRequested + + ", idTokenRequested=" + idTokenRequested + + ", account=" + account + + ", hostedDomainFilter='" + hostedDomainFilter + '\'' + + ", sessionId='" + sessionId + '\'' + + ", forceCodeForRefreshToken=" + forceCodeForRefreshToken + + ", bundle=" + bundle + + ", offlineAccess=" + offlineAccess + + '}'; + } +} diff --git a/play-services-auth/src/main/java/com/google/android/gms/auth/api/identity/AuthorizationResult.java b/play-services-auth/src/main/java/com/google/android/gms/auth/api/identity/AuthorizationResult.java new file mode 100644 index 0000000000..4fe27fa510 --- /dev/null +++ b/play-services-auth/src/main/java/com/google/android/gms/auth/api/identity/AuthorizationResult.java @@ -0,0 +1,55 @@ +/** + * SPDX-FileCopyrightText: 2025 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.auth.api.identity; + +import android.app.PendingIntent; +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.auth.api.signin.GoogleSignInAccount; +import com.google.android.gms.common.api.Scope; +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +import java.util.List; + +@SafeParcelable.Class +public class AuthorizationResult extends AbstractSafeParcelable { + + @Field(1) + public String serverAuthToken; + @Field(2) + public String idToken; + @Field(3) + public String refreshToken; + @Field(4) + public List grantedScopes; + @Field(5) + public GoogleSignInAccount googleSignInAccount; + @Field(6) + public PendingIntent pendingIntent; + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(AuthorizationResult.class); + + @Override + public String toString() { + return "AuthorizationResult{" + + " serverAuthToken='" + serverAuthToken + '\'' + + ", idToken='" + idToken + '\'' + + ", refreshToken='" + refreshToken + '\'' + + ", grantedScopes=" + grantedScopes + + ", googleSignInAccount=" + googleSignInAccount + + ", pendingIntent=" + pendingIntent + + '}'; + } +} diff --git a/play-services-auth/src/main/java/com/google/android/gms/auth/api/identity/VerifyWithGoogleRequest.java b/play-services-auth/src/main/java/com/google/android/gms/auth/api/identity/VerifyWithGoogleRequest.java new file mode 100644 index 0000000000..1d5d60b0a6 --- /dev/null +++ b/play-services-auth/src/main/java/com/google/android/gms/auth/api/identity/VerifyWithGoogleRequest.java @@ -0,0 +1,47 @@ +/** + * SPDX-FileCopyrightText: 2025 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.auth.api.identity; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.api.Scope; +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +import java.util.List; + +@SafeParcelable.Class +public class VerifyWithGoogleRequest extends AbstractSafeParcelable { + + @Field(1) + public List requestedScopes; + @Field(2) + public String serverClientId; + @Field(3) + public boolean offlineAccess; + @Field(4) + public String sessionId; + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(VerifyWithGoogleRequest.class); + + @Override + public String toString() { + return "VerifyWithGoogleRequest{" + + "requestedScopes=" + requestedScopes + + ", serverClientId='" + serverClientId + '\'' + + ", offlineAccess=" + offlineAccess + + ", sessionId='" + sessionId + '\'' + + '}'; + } +} diff --git a/play-services-auth/src/main/java/com/google/android/gms/auth/api/identity/VerifyWithGoogleResult.java b/play-services-auth/src/main/java/com/google/android/gms/auth/api/identity/VerifyWithGoogleResult.java new file mode 100644 index 0000000000..3aae2ccf28 --- /dev/null +++ b/play-services-auth/src/main/java/com/google/android/gms/auth/api/identity/VerifyWithGoogleResult.java @@ -0,0 +1,48 @@ +/** + * SPDX-FileCopyrightText: 2025 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package com.google.android.gms.auth.api.identity; + +import android.app.PendingIntent; +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.api.Scope; +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +import java.util.List; + +@SafeParcelable.Class +public class VerifyWithGoogleResult extends AbstractSafeParcelable { + + @Field(1) + public String serverAuthToken; + @Field(2) + public String idToken; + @Field(3) + public List grantedScopes; + @Field(4) + public PendingIntent pendingIntent; + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static final SafeParcelableCreatorAndWriter CREATOR = findCreator(VerifyWithGoogleResult.class); + + @Override + public String toString() { + return "VerifyWithGoogleResult{" + + "serverAuthToken='" + serverAuthToken + '\'' + + ", idToken='" + idToken + '\'' + + ", grantedScopes=" + grantedScopes + + ", pendingIntent=" + pendingIntent + + '}'; + } +} diff --git a/play-services-base/src/main/java/com/google/android/gms/auth/api/signin/GoogleSignInOptions.java b/play-services-base/src/main/java/com/google/android/gms/auth/api/signin/GoogleSignInOptions.java index c742be477b..33ac99e953 100644 --- a/play-services-base/src/main/java/com/google/android/gms/auth/api/signin/GoogleSignInOptions.java +++ b/play-services-base/src/main/java/com/google/android/gms/auth/api/signin/GoogleSignInOptions.java @@ -158,8 +158,10 @@ public Builder(GoogleSignInOptions options) { this.serverClientId = options.serverClientId; this.account = options.account; this.hostedDomain = options.hostedDomain; - for (GoogleSignInOptionsExtensionParcelable extension : options.extensions) { - extensionMap.put(extension.type, extension); + if (options.extensions != null) { + for (GoogleSignInOptionsExtensionParcelable extension : options.extensions) { + extensionMap.put(extension.type, extension); + } } } diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index cfcfc004cf..794073c830 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -545,6 +545,12 @@ android:name="org.microg.gms.auth.signin.SignInConfigurationService" android:exported="false" /> + + + + + + @@ -1048,7 +1054,6 @@ - diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/credentials/CredentialsService.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/credentials/CredentialsService.kt index c779d08167..b716212c9f 100644 --- a/play-services-core/src/main/kotlin/org/microg/gms/auth/credentials/CredentialsService.kt +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/credentials/CredentialsService.kt @@ -30,7 +30,8 @@ val FEATURES = arrayOf( Feature("auth_api_credentials_save_password", 4), Feature("auth_api_credentials_get_sign_in_intent", 6), Feature("auth_api_credentials_save_account_linking_token", 3), - Feature("auth_api_credentials_get_phone_number_hint_intent", 3) + Feature("auth_api_credentials_get_phone_number_hint_intent", 3), + Feature("auth_api_credentials_verify_with_google", 1) ) class CredentialsService : BaseService(TAG, GmsService.CREDENTIALS) { diff --git a/play-services-core/src/main/kotlin/org/microg/gms/auth/credentials/identity/AuthorizationService.kt b/play-services-core/src/main/kotlin/org/microg/gms/auth/credentials/identity/AuthorizationService.kt new file mode 100644 index 0000000000..4864c43f91 --- /dev/null +++ b/play-services-core/src/main/kotlin/org/microg/gms/auth/credentials/identity/AuthorizationService.kt @@ -0,0 +1,107 @@ +/** + * SPDX-FileCopyrightText: 2025 microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.microg.gms.auth.credentials.identity + +import android.content.Context +import android.util.Log +import androidx.lifecycle.Lifecycle +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.VerifyWithGoogleRequest +import com.google.android.gms.auth.api.identity.VerifyWithGoogleResult +import com.google.android.gms.auth.api.identity.internal.IAuthorizationCallback +import com.google.android.gms.auth.api.identity.internal.IAuthorizationService +import com.google.android.gms.auth.api.identity.internal.IVerifyWithGoogleCallback +import com.google.android.gms.auth.api.signin.GoogleSignInOptions +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.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.credentials.FEATURES +import org.microg.gms.auth.signin.SignInConfigurationService +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.GmsService +import org.microg.gms.common.PackageUtils + +private const val TAG = "AuthorizationService" + +class AuthorizationService : BaseService(TAG, GmsService.AUTHORIZATION) { + + override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) { + Log.d(TAG, "handleServiceRequest start ") + val packageName = PackageUtils.getAndCheckCallingPackage(this, request.packageName) + ?: throw IllegalArgumentException("Missing package name") + val connectionInfo = ConnectionInfo() + connectionInfo.features = FEATURES + callback.onPostInitCompleteWithConnectionInfo( + ConnectionResult.SUCCESS, AuthorizationServiceImpl(this, packageName, this.lifecycle).asBinder(), connectionInfo + ) + } +} + +class AuthorizationServiceImpl(val context: Context, val packageName: String, override val lifecycle: Lifecycle) : IAuthorizationService.Stub(), LifecycleOwner { + + override fun authorize(callback: IAuthorizationCallback?, request: AuthorizationRequest?) { + Log.d(TAG, "Method: authorize called, 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 googleSignInOptions = GoogleSignInOptions.Builder().apply { + request?.requestedScopes?.forEach { requestScopes(it) } + if (request?.idTokenRequested == true && request.serverClientId != null) requestIdToken(request.serverClientId) + if (request?.serverAuthCodeRequested == true && request.serverClientId != null) requestServerAuthCode(request.serverClientId) + }.build() + val signInAccount = performSignIn(context, packageName, googleSignInOptions, account, false) + callback?.onAuthorized(Status.SUCCESS, AuthorizationResult().apply { + serverAuthToken = signInAccount?.serverAuthCode + idToken = signInAccount?.idToken + grantedScopes = signInAccount?.grantedScopes?.toList() + googleSignInAccount = signInAccount + }.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) + if (account == null) { + Log.d(TAG, "Method: authorize called, but account is null") + callback?.onVerifed(Status.CANCELED, null) + return@launchWhenStarted + } + if (request?.offlineAccess == true && request.serverClientId != null) { + val googleSignInOptions = GoogleSignInOptions.Builder().apply { + request.requestedScopes?.forEach { requestScopes(it) } + requestServerAuthCode(request.serverClientId) + }.build() + val authResponse = getServerAuthTokenManager(context, packageName, googleSignInOptions, account)?.let { + withContext(Dispatchers.IO) { it.requestAuth(true) } + } + callback?.onVerifed(Status.SUCCESS, VerifyWithGoogleResult().apply { + serverAuthToken = authResponse?.auth + grantedScopes = authResponse?.grantedScopes?.split(" ")?.map { Scope(it) }?.toList() ?: googleSignInOptions.scopeUris.toList() + }) + return@launchWhenStarted + } + callback?.onVerifed(Status.CANCELED, null) + } + } + +} \ No newline at end of file