Merge branch 'final-1.1-fixes' into 'master'

Final 1.1 fixes

Closes #1310

See merge request fdroid/fdroidclient!658
This commit is contained in:
Hans-Christoph Steiner 2018-03-21 19:06:58 +00:00
commit 51b22d50b6
6 changed files with 51 additions and 68 deletions

View File

@ -25,7 +25,6 @@ import android.content.pm.PackageInfo;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.support.v4.content.FileProvider; import android.support.v4.content.FileProvider;
import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.SanitizedFile; import org.fdroid.fdroid.data.SanitizedFile;
@ -34,7 +33,8 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
/** /**
* This class has helper methods for preparing apks for installation. * Helper methods for preparing APKs and arbitrary files for installation,
* either locally or for sending via bluetooth.
* <p/> * <p/>
* APK handling for installations: * APK handling for installations:
* 1. APKs are downloaded into a cache directory that is either created on SD card * 1. APKs are downloaded into a cache directory that is either created on SD card
@ -56,34 +56,44 @@ public class ApkFileProvider extends FileProvider {
} }
/** /**
* Copies the APK into private data directory of F-Droid and returns a "file" or "content" Uri * Copies the APK into private data directory of F-Droid and returns a
* to be used for installation. * {@code file://} or {@code content://} URI to be used for the
* actual installation process. Only APKs will ever use a {@code content://}
* URI, any other file will always use a {@code file://} URI since F-Droid
* itself handles their whole installation process.
*/ */
public static Uri getSafeUri(Context context, Uri localApkUri, Apk expectedApk, boolean useContentUri) public static Uri getSafeUri(Context context, Uri localApkUri, Apk expectedApk)
throws IOException { throws IOException {
File apkFile = new File(localApkUri.getPath()); File apkFile = new File(localApkUri.getPath());
SanitizedFile tempApkFile = ApkCache.copyApkFromCacheToFiles(context, apkFile, expectedApk); SanitizedFile tempApkFile = ApkCache.copyApkFromCacheToFiles(context, apkFile, expectedApk);
return getSafeUri(context, tempApkFile, useContentUri); return getSafeUri(context, tempApkFile,
Build.VERSION.SDK_INT >= 24 && expectedApk.isApk());
} }
private static Uri getSafeUri(Context context, SanitizedFile tempApkFile, boolean useContentUri) { /**
* Return a {@link Uri} for all install processes to install this package
* from. This supports APKs and all other supported files. It also
* supports all installation methods, e.g. default, privileged, etc.
* It can return either a {@code content://} or {@code file://} URI.
* <p>
* APKs need to be world readable, so that the Android system installer
* is able to read it. Saving it into external storage to send it to the
* installer have access is insecure, because apps with permission to write
* to the external storage can overwrite the app between F-Droid asking for
* it to be installed and the installer actually installing it.
*/
private static Uri getSafeUri(Context context, SanitizedFile tempFile, boolean useContentUri) {
if (useContentUri) { if (useContentUri) {
// return a content Uri using support libs FileProvider Uri apkUri = getUriForFile(context, AUTHORITY, tempFile);
Uri apkUri = getUriForFile(context, AUTHORITY, tempApkFile);
context.grantUriPermission("org.fdroid.fdroid.privileged", apkUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); context.grantUriPermission("org.fdroid.fdroid.privileged", apkUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
context.grantUriPermission("com.android.bluetooth", apkUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); context.grantUriPermission("com.android.bluetooth", apkUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
return apkUri; return apkUri;
} }
// Need the apk to be world readable, so that the installer is able to read it. tempFile.setReadable(true, false);
// Note that saving it into external storage for the purpose of letting the installer
// have access is insecure, because apps with permission to write to the external
// storage can overwrite the app between F-Droid asking for it to be installed and
// the installer actually installing it.
tempApkFile.setReadable(true, false);
return Uri.fromFile(tempApkFile); return Uri.fromFile(tempFile);
} }
} }

View File

@ -24,8 +24,6 @@ import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.Apk;
/** /**
@ -82,10 +80,4 @@ public class DefaultInstaller extends Installer {
protected boolean isUnattended() { protected boolean isUnattended() {
return false; return false;
} }
@Override
protected boolean supportsContentUri() {
// Android N only supports content Uris
return Build.VERSION.SDK_INT >= 24;
}
} }

View File

@ -24,7 +24,6 @@ import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.BuildConfig;
import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.privileged.install.InstallExtensionDialogActivity; import org.fdroid.fdroid.privileged.install.InstallExtensionDialogActivity;
@ -33,11 +32,16 @@ import java.io.File;
/** /**
* Special Installer that is only useful to install the Privileged Extension apk * Special Installer that is only useful to install the Privileged Extension apk
* as a privileged app into the system partition of Android. * as a privileged app into the system partition of Android. It is deprecated
* because it cannot work on Android versions newer than {@code android-20} or so,
* due to increased SELinux enforcement that restricts what even root can do.
* <p/> * <p/>
* This is installer requires user interaction and thus install/uninstall directly * This is installer requires user interaction and thus install/uninstall directly
* return PendingIntents. * return PendingIntents.
*
* @see <a href="https://www.androidauthority.com/chainfire-rooting-android-lollipop-541458/">Chainfire talks Android Lollipop and the future of rooting</a>
*/ */
@Deprecated
public class ExtensionInstaller extends Installer { public class ExtensionInstaller extends Installer {
ExtensionInstaller(Context context, Apk apk) { ExtensionInstaller(Context context, Apk apk) {
@ -94,9 +98,4 @@ public class ExtensionInstaller extends Installer {
protected boolean isUnattended() { protected boolean isUnattended() {
return false; return false;
} }
@Override
protected boolean supportsContentUri() {
return false;
}
} }

View File

@ -85,10 +85,4 @@ public class FileInstaller extends Installer {
protected boolean isUnattended() { protected boolean isUnattended() {
return false; return false;
} }
@Override
protected boolean supportsContentUri() {
return false;
}
} }

View File

@ -30,8 +30,6 @@ import android.os.Build;
import android.os.PatternMatcher; import android.os.PatternMatcher;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.ApkProvider; import org.fdroid.fdroid.data.ApkProvider;
@ -195,7 +193,7 @@ public abstract class Installer {
sendBroadcastUninstall(action, pendingIntent, null); sendBroadcastUninstall(action, pendingIntent, null);
} }
void sendBroadcastUninstall(String action, PendingIntent pendingIntent, String errorMessage) { private void sendBroadcastUninstall(String action, PendingIntent pendingIntent, String errorMessage) {
Uri uri = Uri.fromParts("package", apk.packageName, null); Uri uri = Uri.fromParts("package", apk.packageName, null);
Intent intent = new Intent(action); Intent intent = new Intent(action);
@ -236,20 +234,29 @@ public abstract class Installer {
} }
/** /**
* Install apk * Install apk given the URI that points to the local APK file, and the
* download URI to identify which session this belongs to. This first
* moves the APK file to private directory for the installation process
* to read from. Then the hash of the APK is checked against the
* {@link Apk} instance provided when this {@code Installer} object was
* instantiated. The list of permissions in the APK file and the
* {@code Apk} instance are compared, if they do not match, then the user
* is prompted with the system installer dialog, which shows all the
* permissions that the APK is requesting.
* *
* @param localApkUri points to the local copy of the APK to be installed * @param localApkUri points to the local copy of the APK to be installed
* @param downloadUri serves as the unique ID for all actions related to the * @param downloadUri serves as the unique ID for all actions related to the
* installation of that specific APK * installation of that specific APK
* @see InstallManagerService
* @see <a href="https://issuetracker.google.com/issues/37091886">ACTION_INSTALL_PACKAGE Fails For Any Possible Uri</a>
*/ */
public void installPackage(Uri localApkUri, Uri downloadUri) { public void installPackage(Uri localApkUri, Uri downloadUri) {
Uri sanitizedUri; Uri sanitizedUri;
try { try {
// move apk file to private directory for installation and check hash sanitizedUri = ApkFileProvider.getSafeUri(context, localApkUri, apk);
sanitizedUri = ApkFileProvider.getSafeUri(context, localApkUri, apk, supportsContentUri());
} catch (IOException e) { } catch (IOException e) {
Log.e(TAG, e.getMessage(), e); Utils.debugLog(TAG, e.getMessage(), e);
sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED, e.getMessage()); sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED, e.getMessage());
return; return;
} }
@ -259,7 +266,7 @@ public abstract class Installer {
ApkVerifier apkVerifier = new ApkVerifier(context, localApkUri, apk); ApkVerifier apkVerifier = new ApkVerifier(context, localApkUri, apk);
apkVerifier.verifyApk(); apkVerifier.verifyApk();
} catch (ApkVerifier.ApkVerificationException e) { } catch (ApkVerifier.ApkVerificationException e) {
Log.e(TAG, e.getMessage(), e); Utils.debugLog(TAG, e.getMessage(), e);
sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED, e.getMessage()); sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED, e.getMessage());
return; return;
} catch (ApkVerifier.ApkPermissionUnequalException e) { } catch (ApkVerifier.ApkPermissionUnequalException e) {
@ -267,17 +274,10 @@ public abstract class Installer {
// and an unattended installer is used, a wrong permission screen // and an unattended installer is used, a wrong permission screen
// has been shown, thus fallback to AOSP DefaultInstaller! // has been shown, thus fallback to AOSP DefaultInstaller!
if (isUnattended()) { if (isUnattended()) {
Log.e(TAG, e.getMessage(), e); Utils.debugLog(TAG, e.getMessage(), e);
Log.e(TAG, "Falling back to AOSP DefaultInstaller!"); Utils.debugLog(TAG, "Falling back to AOSP DefaultInstaller!");
DefaultInstaller defaultInstaller = new DefaultInstaller(context, apk); DefaultInstaller defaultInstaller = new DefaultInstaller(context, apk);
// https://code.google.com/p/android/issues/detail?id=205827
if (Build.VERSION.SDK_INT >= 24) {
// content scheme for N and above
defaultInstaller.installPackageInternal(sanitizedUri, downloadUri); defaultInstaller.installPackageInternal(sanitizedUri, downloadUri);
} else {
// file scheme for below N
defaultInstaller.installPackageInternal(localApkUri, downloadUri);
}
return; return;
} }
} }
@ -298,10 +298,4 @@ public abstract class Installer {
* uninstall activities, without the system enforcing a user prompt. * uninstall activities, without the system enforcing a user prompt.
*/ */
protected abstract boolean isUnattended(); protected abstract boolean isUnattended();
/**
* @return true if the Installer supports content Uris and not just file Uris
*/
protected abstract boolean supportsContentUri();
} }

View File

@ -27,7 +27,6 @@ import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.os.RemoteException; import android.os.RemoteException;
import android.util.Log; import android.util.Log;
@ -404,9 +403,4 @@ public class PrivilegedInstaller extends Installer {
protected boolean isUnattended() { protected boolean isUnattended() {
return true; return true;
} }
@Override
protected boolean supportsContentUri() {
return Build.VERSION.SDK_INT >= 24;
}
} }