Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0ed4d834 authored by Jonathan Klee's avatar Jonathan Klee
Browse files

Implement ContentProviderToken

Add possibility to get an auth token from a content provider
during the Licensing app requests.
parent 726f7787
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -9,9 +9,14 @@
        android:name="com.android.vending.CHECK_LICENSE"
        android:protectionLevel="normal" />

    <permission
        android:name="org.microg.gms.permission.AUTH_TOKEN_PROVIDER"
        android:protectionLevel="signature" />

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="org.microg.gms.permission.AUTH_TOKEN_PROVIDER" />

    <uses-permission
        android:name="android.permission.USE_CREDENTIALS"
+73 −0
Original line number Diff line number Diff line

/*
 * SPDX-FileCopyrightText: 2023 microG Project Team
 * SPDX-FileCopyrightText: 2023 E FOUNDATION
 * SPDX-License-Identifier: Apache-2.0
 */

package com.android.vending.licensing;

import android.content.Context;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;

class ContentProviderToken {

   private static final String TAG = "ContentProviderToken";
   private static final String TOKEN_AUTHORITIES = "content://org.microg.gms.token.provider";
   private static final String TOKEN_COLUMN_INDEX = "token";
   private static final String AUTH_TOKEN_PROVIDER_PERMISSION =
           "org.microg.gms.permission.AUTH_TOKEN_PROVIDER";

   private final Context context;

   private String token;

   public ContentProviderToken(Context context) {
      this.context = context;
   }

   public String fetch() {

      if (context.getPackageManager()
              .checkPermission(AUTH_TOKEN_PROVIDER_PERMISSION, context.getPackageName())
              != PackageManager.PERMISSION_GRANTED) {
         Log.e(TAG, "missing permission: " + AUTH_TOKEN_PROVIDER_PERMISSION);
         return null;
      }

      Cursor cursor = context.getContentResolver().query(
              Uri.parse(TOKEN_AUTHORITIES),
              null,
              null,
              null,
              null
      );

      if (cursor == null || !cursor.moveToFirst()) {
         return null;
      }

      int columnIndex = cursor.getColumnIndex(TOKEN_COLUMN_INDEX);
      if (columnIndex == -1) {
         return null;
      }

      String token = cursor.getString(columnIndex);
      if (token.isEmpty()) {
         return null;
      }

      this.token = token;

      Log.i(TAG, "Token fetched from Store: " + token);
      cursor.close();
      return token;
   }

   public String getToken() {
      return token;
   }
}
+54 −31
Original line number Diff line number Diff line
@@ -14,7 +14,6 @@ import android.os.RemoteException;
import android.util.Log;

import com.android.vending.V1Container;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;

@@ -82,7 +81,8 @@ public abstract class LicenseChecker<D, R> {
    public abstract LicenseRequest<?> createRequest(String packageName, String auth, int versionCode, D data,
                                             BiConsumer<Integer, R> then, Response.ErrorListener errorListener);

    public void checkLicense(Account account, AccountManager accountManager, String androidId,
    public void checkLicense(Account account, ContentProviderToken contentProviderToken,
                             AccountManager accountManager, String androidId,
                             String packageName, PackageManager packageManager,
                             RequestQueue queue, D queryData,
                             BiConsumerWithException<Integer, R, RemoteException> onResult)
@@ -94,9 +94,11 @@ public abstract class LicenseChecker<D, R> {
            // Verify caller identity
            if (packageInfo.applicationInfo.uid != getCallingUid()) {
                Log.e(TAG,
                    "an app illegally tried to request licenses for another app (caller: " + getCallingUid() + ")");
                        "an app illegally tried to request licenses for another app (caller: "
                                + getCallingUid() + ")");
                onResult.accept(ERROR_NON_MATCHING_UID, null);
            } else {
                return;
            }

            BiConsumer<Integer, R> onRequestFinished = (Integer integer, R r) -> {
                try {
@@ -113,6 +115,10 @@ public abstract class LicenseChecker<D, R> {
                safeSendResult(onResult, ERROR_CONTACTING_SERVER, null);
            };

            if (account == null) {
                queueRequest(queue, androidId, packageName, contentProviderToken.getToken(),
                        versionCode, queryData, onRequestFinished, onRequestError);
            } else {
                accountManager.getAuthToken(
                        account, AUTH_TOKEN_SCOPE, false,
                        future -> {
@@ -123,12 +129,13 @@ public abstract class LicenseChecker<D, R> {
                                request.ANDROID_ID = Long.decode("0x" + androidId);
                                request.setShouldCache(false);
                                queue.add(request);
                        } catch (AuthenticatorException | IOException | OperationCanceledException e) {
                            } catch (AuthenticatorException | IOException |
                                     OperationCanceledException e) {
                                safeSendResult(onResult, ERROR_CONTACTING_SERVER, null);
                            }

                        }, null);
            }

        } catch (PackageManager.NameNotFoundException e) {
            Log.e(TAG, "an app tried to request licenses for package " + packageName + ", which does not exist");
            onResult.accept(ERROR_INVALID_PACKAGE_NAME, null);
@@ -145,6 +152,22 @@ public abstract class LicenseChecker<D, R> {
        }
    }

    private void queueRequest(
            RequestQueue queue,
            String androidId,
            String packageName,
            String authToken,
            int versionCode,
            D queryData,
            BiConsumer<Integer, R> onRequestFinished,
            Response.ErrorListener onRequestError
    ) {
        LicenseRequest<?> request = createRequest(packageName, authToken,
                versionCode, queryData, onRequestFinished, onRequestError);
        request.setShouldCache(false);
        queue.add(request);
    }

    // Implementations

    public static class V1 extends LicenseChecker<Long, Tuple<String, String>> {
+34 −14
Original line number Diff line number Diff line
@@ -25,12 +25,13 @@ import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;

import org.microg.gms.auth.AuthConstants;
import org.microg.gms.profile.Build;
import org.microg.gms.profile.ProfileManager;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;

@@ -41,6 +42,7 @@ public class LicensingService extends Service {
    private RequestQueue queue;
    private AccountManager accountManager;
    private LicenseServiceNotificationRunnable notificationRunnable;
    private ContentProviderToken contentProviderToken;

    private static final String KEY_V2_RESULT_JWT = "LICENSE_DATA";

@@ -56,24 +58,27 @@ public class LicensingService extends Service {
        public void checkLicense(long nonce, String packageName, ILicenseResultListener listener) throws RemoteException {
            Log.v(TAG, "checkLicense(" + nonce + ", " + packageName + ")");

            Account[] accounts = accountManager.getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE);
            List<Account> accounts = fetchAccounts();
            PackageManager packageManager = getPackageManager();

            if (accounts.length == 0) {
            if (accounts.size() == 0) {
                handleNoAccounts(packageName, packageManager);
            } else {
                checkLicense(nonce, packageName, packageManager, listener, new LinkedList<>(Arrays.asList(accounts)));
                checkLicense(nonce, packageName, packageManager, listener, new LinkedList<>(accounts), contentProviderToken);
            }
        }

        private void checkLicense(long nonce, String packageName, PackageManager packageManager,
                                  ILicenseResultListener listener, Queue<Account> remainingAccounts) throws RemoteException {
                                  ILicenseResultListener listener, Queue<Account> remainingAccounts,
                                  ContentProviderToken contentProviderToken) throws RemoteException {
            new LicenseChecker.V1().checkLicense(
                remainingAccounts.poll(), accountManager, androidId, packageName, packageManager,
                remainingAccounts.poll(), contentProviderToken, accountManager, androidId,
                packageName, packageManager,
                queue, nonce,
                (responseCode, stringTuple) -> {
                    if (responseCode != LICENSED && !remainingAccounts.isEmpty()) {
                        checkLicense(nonce, packageName, packageManager, listener, remainingAccounts);
                        checkLicense(nonce, packageName, packageManager, listener,
                                remainingAccounts, contentProviderToken);
                    } else {
                        listener.verifyLicense(responseCode, stringTuple.a, stringTuple.b);
                    }
@@ -85,21 +90,23 @@ public class LicensingService extends Service {
        public void checkLicenseV2(String packageName, ILicenseV2ResultListener listener, Bundle extraParams) throws RemoteException {
            Log.v(TAG, "checkLicenseV2(" + packageName + ", " + extraParams + ")");

            Account[] accounts = accountManager.getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE);
            List<Account> accounts = fetchAccounts();
            PackageManager packageManager = getPackageManager();

            if (accounts.length == 0) {
            if (accounts.size() == 0) {
                handleNoAccounts(packageName, packageManager);
            } else {
                checkLicenseV2(packageName, packageManager, listener, extraParams, new LinkedList<>(Arrays.asList(accounts)));
                checkLicenseV2(packageName, packageManager, listener, extraParams, new LinkedList<>(accounts), contentProviderToken);
            }
        }

        private void checkLicenseV2(String packageName, PackageManager packageManager,
                                    ILicenseV2ResultListener listener, Bundle extraParams,
                                    Queue<Account> remainingAccounts) throws RemoteException {
                                    Queue<Account> remainingAccounts,
                                    ContentProviderToken contentProviderToken) throws RemoteException {
            new LicenseChecker.V2().checkLicense(
                remainingAccounts.poll(), accountManager, androidId, packageName, packageManager, queue, Unit.INSTANCE,
                    remainingAccounts.poll(), contentProviderToken, accountManager, androidId,
                    packageName, packageManager, queue, Unit.INSTANCE,
                    (responseCode, data) -> {
                    /*
                     * Suppress failures on V2. V2 is commonly used by free apps whose checker
@@ -114,7 +121,7 @@ public class LicensingService extends Service {

                        listener.verifyLicense(responseCode, bundle);
                    } else if (!remainingAccounts.isEmpty()) {
                        checkLicenseV2(packageName, packageManager, listener, extraParams, remainingAccounts);
                        checkLicenseV2(packageName, packageManager, listener, extraParams, remainingAccounts, contentProviderToken);
                    } else {
                        Log.i(TAG, "Suppressed negative license result for package " + packageName);
                    }
@@ -135,6 +142,17 @@ public class LicensingService extends Service {
            }
            notificationRunnable.run();
        }

        private List<Account> fetchAccounts() {
            Account[] accountArray = accountManager.getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE);
            List<Account> accounts = new ArrayList<>(Arrays.asList(accountArray));
            if (contentProviderToken.getToken() != null) {
                // we add a null account which is considered as a token from the content provider
                accounts.add(null);
            }

            return accounts;
        }
    };

    public IBinder onBind(Intent intent) {
@@ -167,6 +185,8 @@ public class LicensingService extends Service {
        queue = Volley.newRequestQueue(this);
        accountManager = AccountManager.get(this);
        notificationRunnable = new LicenseServiceNotificationRunnable(this);
        contentProviderToken = new ContentProviderToken(this);
        contentProviderToken.fetch();

        return mLicenseService;
    }