diff --git a/app/src/androidTest/assets/extendedPerms.xml b/app/src/androidTest/assets/extendedPerms.xml
new file mode 100644
index 000000000..a573dfa28
--- /dev/null
+++ b/app/src/androidTest/assets/extendedPerms.xml
@@ -0,0 +1,125 @@
+
+
+
+
+ This is just a test of the extended permissions attributes.
+
+
+
+
+ at.bitfire.davdroid
+ 2013-10-13
+ 2016-06-26
+ DAVdroid
+ Contacts and Calendar sync
+ at.bitfire.davdroid.107.png
+ apk generated from urzip to test extended permissions
+ GPLv3
+ Internet
+ Internet
+ https://davdroid.bitfire.at/
+ https://davdroid.bitfire.at/source/
+ https://davdroid.bitfire.at/forums/
+ https://gitlab.com/bitfireAT/davdroid/tags
+ https://davdroid.bitfire.at/donate/
+ 1KSCy7RHztKuhW9fLLaUYqdwdC2iwbejZU
+ 2100160
+ 1.1.1.2
+ 107
+
+ 1.3.2-FAKE
+ 117
+ org.fdroid.extendedpermissionstest.apk
+ at.bitfire.davdroid_116_src.tar.gz
+ f1aa02257e99c167d2ea9b0e9525c3ce7c181fe2e7f4dd00b65dd81ed2e27a62
+
+ 03542175324d067b4c36582242f8aecc
+ 3298864
+ 14
+ 23
+ 2016-09-22
+
+ READ_EXTERNAL_STORAGE,WRITE_SYNC_SETTINGS,ACCESS_NETWORK_STATE,WRITE_EXTERNAL_STORAGE,WRITE_CONTACTS,ACCESS_WIFI_STATE,REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,WRITE_CALENDAR,READ_CONTACTS,READ_SYNC_SETTINGS,MANAGE_ACCOUNTS,INTERNET,AUTHENTICATE_ACCOUNTS,GET_ACCOUNTS,READ_CALENDAR,READ_SYNC_STATS
+
+
+
+
+
+
+
+
+
+
+ 1.3.1-ose
+ 116
+ at.bitfire.davdroid_116.apk
+ at.bitfire.davdroid_116_src.tar.gz
+ f1aa02257e99c167d2ea9b0e9525c3ce7c181fe2e7f4dd00b65dd81ed2e27a62
+
+ 03542175324d067b4c36582242f8aecc
+ 3298864
+ 14
+ 24
+ 2016-09-21
+
+ READ_EXTERNAL_STORAGE,WRITE_SYNC_SETTINGS,ACCESS_NETWORK_STATE,WRITE_EXTERNAL_STORAGE,org.dmfs.permission.READ_TASKS,WRITE_CONTACTS,ACCESS_WIFI_STATE,REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,WRITE_CALENDAR,READ_CONTACTS,READ_SYNC_SETTINGS,MANAGE_ACCOUNTS,INTERNET,AUTHENTICATE_ACCOUNTS,GET_ACCOUNTS,READ_CALENDAR,org.dmfs.permission.WRITE_TASKS
+
+
+
+
+
+
+
+
+ 1.1.1.2
+ 107
+ at.bitfire.davdroid_107.apk
+ at.bitfire.davdroid_107_src.tar.gz
+ 9a616f2e97bf8cf012baf896f95667dea4e3ce3252b31c5715073638a9fcc3d4
+
+ 03542175324d067b4c36582242f8aecc
+ 3134363
+ 14
+ 23
+ 2016-06-26
+
+ org.dmfs.permission.READ_TASKS,READ_EXTERNAL_STORAGE,WRITE_CONTACTS,GET_ACCOUNTS,AUTHENTICATE_ACCOUNTS,WRITE_EXTERNAL_STORAGE,READ_CALENDAR,ACCESS_WIFI_STATE,org.dmfs.permission.WRITE_TASKS,ACCESS_NETWORK_STATE,WRITE_CALENDAR,READ_CONTACTS,READ_SYNC_SETTINGS,INTERNET,MANAGE_ACCOUNTS,WRITE_SYNC_SETTINGS
+
+
+
+ 1.1.1.1
+ 105
+ at.bitfire.davdroid_105.apk
+ at.bitfire.davdroid_105_src.tar.gz
+ 4a0408c61536a1cc1028cea4273adbde2e57dfa2b12d93c3b52f4c3d095e2849
+
+ 03542175324d067b4c36582242f8aecc
+ 3131567
+ 14
+ 23
+ 2016-06-24
+
+ org.dmfs.permission.READ_TASKS,READ_EXTERNAL_STORAGE,WRITE_CONTACTS,GET_ACCOUNTS,AUTHENTICATE_ACCOUNTS,WRITE_EXTERNAL_STORAGE,READ_CALENDAR,ACCESS_WIFI_STATE,org.dmfs.permission.WRITE_TASKS,ACCESS_NETWORK_STATE,WRITE_CALENDAR,READ_CONTACTS,READ_SYNC_SETTINGS,INTERNET,MANAGE_ACCOUNTS,WRITE_SYNC_SETTINGS
+
+
+
+ 1.1.1
+ 104
+ at.bitfire.davdroid_104.apk
+ at.bitfire.davdroid_104_src.tar.gz
+ 09ba34996177efe8b1498a93fe6521ab84efab3bccb0f42449116e80b59e5b56
+
+ 03542175324d067b4c36582242f8aecc
+ 3131367
+ 14
+ 23
+ 2016-06-22
+
+ org.dmfs.permission.READ_TASKS,READ_EXTERNAL_STORAGE,WRITE_CONTACTS,GET_ACCOUNTS,AUTHENTICATE_ACCOUNTS,WRITE_EXTERNAL_STORAGE,READ_CALENDAR,ACCESS_WIFI_STATE,org.dmfs.permission.WRITE_TASKS,ACCESS_NETWORK_STATE,WRITE_CALENDAR,READ_CONTACTS,READ_SYNC_SETTINGS,INTERNET,MANAGE_ACCOUNTS,WRITE_SYNC_SETTINGS
+
+
+
+
+
diff --git a/app/src/androidTest/assets/org.fdroid.extendedpermissionstest.apk b/app/src/androidTest/assets/org.fdroid.extendedpermissionstest.apk
new file mode 100644
index 000000000..9e2377dc2
Binary files /dev/null and b/app/src/androidTest/assets/org.fdroid.extendedpermissionstest.apk differ
diff --git a/app/src/androidTest/java/org/fdroid/fdroid/installer/ApkVerifierTest.java b/app/src/androidTest/java/org/fdroid/fdroid/installer/ApkVerifierTest.java
index 9b9e1eb9d..28bfffdea 100644
--- a/app/src/androidTest/java/org/fdroid/fdroid/installer/ApkVerifierTest.java
+++ b/app/src/androidTest/java/org/fdroid/fdroid/installer/ApkVerifierTest.java
@@ -21,18 +21,32 @@ package org.fdroid.fdroid.installer;
import android.app.Instrumentation;
import android.net.Uri;
+import android.os.Build;
+import android.support.annotation.NonNull;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
import org.fdroid.fdroid.AssetUtils;
+import org.fdroid.fdroid.RepoXMLHandler;
+import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.compat.FileCompatTest;
import org.fdroid.fdroid.data.Apk;
+import org.fdroid.fdroid.data.Repo;
+import org.fdroid.fdroid.mock.RepoDetails;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -40,10 +54,7 @@ import static org.junit.Assert.fail;
* This test checks the ApkVerifier by parsing a repo from permissionsRepo.xml
* and checking the listed permissions against the ones specified in apks' AndroidManifest,
* which have been specifically generated for this test.
- * - the apk file name must match the package name in the xml
- * - the versionName of listed apks inside the repo have either a good or bad outcome.
- * this must be defined in GOOD_VERSION_NAMES and BAD_VERSION_NAMES.
- *
+ *
* NOTE: This androidTest cannot run as a Robolectric test because the
* required methods from PackageManger are not included in Robolectric's Android API.
* java.lang.NoClassDefFoundError: java/util/jar/StrictJarFile
@@ -51,11 +62,14 @@ import static org.junit.Assert.fail;
*/
@RunWith(AndroidJUnit4.class)
public class ApkVerifierTest {
+ public static final String TAG = "ApkVerifierTest";
Instrumentation instrumentation;
File sdk14Apk;
File minMaxApk;
+ private File extendedPermissionsApk;
+ private File extendedPermsXml;
@Before
public void setUp() {
@@ -71,8 +85,18 @@ public class ApkVerifierTest {
"org.fdroid.permissions.minmax.apk",
dir
);
+ extendedPermissionsApk = AssetUtils.copyAssetToDir(instrumentation.getContext(),
+ "org.fdroid.extendedpermissionstest.apk",
+ dir
+ );
+ extendedPermsXml = AssetUtils.copyAssetToDir(instrumentation.getContext(),
+ "extendedPerms.xml",
+ dir
+ );
assertTrue(sdk14Apk.exists());
assertTrue(minMaxApk.exists());
+ assertTrue(extendedPermissionsApk.exists());
+ assertTrue(extendedPermsXml.exists());
}
@Test
@@ -80,7 +104,7 @@ public class ApkVerifierTest {
Apk apk = new Apk();
apk.packageName = "org.fdroid.permissions.sdk14";
apk.targetSdkVersion = 14;
- apk.requestedPermissions = new String[]{
+ String[] noPrefixPermissions = new String[]{
"AUTHENTICATE_ACCOUNTS",
"MANAGE_ACCOUNTS",
"READ_PROFILE",
@@ -98,9 +122,12 @@ public class ApkVerifierTest {
"WRITE_CALL_LOG", // implied-permission!
"READ_CALL_LOG", // implied-permission!
};
+ for (int i = 0; i < noPrefixPermissions.length; i++) {
+ noPrefixPermissions[i] = RepoXMLHandler.fdroidToAndroidPermission(noPrefixPermissions[i]);
+ }
+ apk.requestedPermissions = noPrefixPermissions;
Uri uri = Uri.fromFile(sdk14Apk);
-
ApkVerifier apkVerifier = new ApkVerifier(instrumentation.getContext(), uri, apk);
try {
@@ -111,6 +138,31 @@ public class ApkVerifierTest {
}
}
+ @Test(expected = ApkVerifier.ApkPermissionUnequalException.class)
+ public void testWithMinMax()
+ throws ApkVerifier.ApkPermissionUnequalException, ApkVerifier.ApkVerificationException {
+ Apk apk = new Apk();
+ apk.packageName = "org.fdroid.permissions.minmax";
+ apk.targetSdkVersion = 24;
+ ArrayList permissionsList = new ArrayList<>();
+ permissionsList.add("android.permission.READ_CALENDAR");
+ if (Build.VERSION.SDK_INT <= 18) {
+ permissionsList.add("android.permission.WRITE_EXTERNAL_STORAGE");
+ }
+ if (Build.VERSION.SDK_INT >= 23) {
+ permissionsList.add("android.permission.ACCESS_FINE_LOCATION");
+ }
+ apk.requestedPermissions = permissionsList.toArray(new String[permissionsList.size()]);
+
+ Uri uri = Uri.fromFile(minMaxApk);
+ ApkVerifier apkVerifier = new ApkVerifier(instrumentation.getContext(), uri, apk);
+ apkVerifier.verifyApk();
+
+ permissionsList.add("ADDITIONAL_PERMISSION");
+ apk.requestedPermissions = permissionsList.toArray(new String[permissionsList.size()]);
+ apkVerifier.verifyApk();
+ }
+
@Test
public void testWithPrefix() {
Apk apk = new Apk();
@@ -151,8 +203,9 @@ public class ApkVerifierTest {
* Additional permissions are okay. The user is simply
* warned about a permission that is not used inside the apk
*/
- @Test
- public void testAdditionalPermission() {
+ @Test(expected = ApkVerifier.ApkPermissionUnequalException.class)
+ public void testAdditionalPermission()
+ throws ApkVerifier.ApkPermissionUnequalException, ApkVerifier.ApkVerificationException {
Apk apk = new Apk();
apk.packageName = "org.fdroid.permissions.sdk14";
apk.targetSdkVersion = 14;
@@ -173,19 +226,12 @@ public class ApkVerifierTest {
"android.permission.WRITE_SYNC_SETTINGS",
"android.permission.WRITE_CALL_LOG", // implied-permission!
"android.permission.READ_CALL_LOG", // implied-permission!
- "NEW_PERMISSION",
+ "android.permission.FAKE_NEW_PERMISSION",
};
Uri uri = Uri.fromFile(sdk14Apk);
-
ApkVerifier apkVerifier = new ApkVerifier(instrumentation.getContext(), uri, apk);
-
- try {
- apkVerifier.verifyApk();
- } catch (ApkVerifier.ApkVerificationException | ApkVerifier.ApkPermissionUnequalException e) {
- e.printStackTrace();
- fail(e.getMessage());
- }
+ apkVerifier.verifyApk();
}
/**
@@ -231,4 +277,73 @@ public class ApkVerifierTest {
}
}
+ @Test
+ public void testExtendedPerms() throws IOException,
+ ApkVerifier.ApkPermissionUnequalException, ApkVerifier.ApkVerificationException {
+ RepoDetails actualDetails = getFromFile(extendedPermsXml);
+ HashSet expectedSet = new HashSet<>(Arrays.asList(new String[]{
+ "android.permission.ACCESS_NETWORK_STATE",
+ "android.permission.ACCESS_WIFI_STATE",
+ "android.permission.INTERNET",
+ "android.permission.READ_SYNC_STATS",
+ "android.permission.READ_SYNC_SETTINGS",
+ "android.permission.WRITE_SYNC_SETTINGS",
+ "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS",
+ "android.permission.READ_CONTACTS",
+ "android.permission.WRITE_CONTACTS",
+ "android.permission.READ_CALENDAR",
+ "android.permission.WRITE_CALENDAR",
+ }));
+ if (Build.VERSION.SDK_INT <= 18) {
+ expectedSet.add("android.permission.READ_EXTERNAL_STORAGE");
+ expectedSet.add("android.permission.WRITE_EXTERNAL_STORAGE");
+ }
+ if (Build.VERSION.SDK_INT <= 22) {
+ expectedSet.add("android.permission.GET_ACCOUNTS");
+ expectedSet.add("android.permission.AUTHENTICATE_ACCOUNTS");
+ expectedSet.add("android.permission.MANAGE_ACCOUNTS");
+ }
+ if (Build.VERSION.SDK_INT >= 23) {
+ expectedSet.add("android.permission.CAMERA");
+ if (Build.VERSION.SDK_INT <= 23) {
+ expectedSet.add("android.permission.CALL_PHONE");
+ }
+ }
+ Apk apk = actualDetails.apks.get(0);
+ HashSet actualSet = new HashSet<>(Arrays.asList(apk.requestedPermissions));
+ for (String permission : expectedSet) {
+ if (!actualSet.contains(permission)) {
+ Log.i(TAG, permission + " in expected but not actual! (android-"
+ + Build.VERSION.SDK_INT + ")");
+ }
+ }
+ for (String permission : actualSet) {
+ if (!expectedSet.contains(permission)) {
+ Log.i(TAG, permission + " in actual but not expected! (android-"
+ + Build.VERSION.SDK_INT + ")");
+ }
+ }
+ String[] expectedPermissions = expectedSet.toArray(new String[expectedSet.size()]);
+ assertTrue(ApkVerifier.requestedPermissionsEqual(expectedPermissions, apk.requestedPermissions));
+
+ String[] badPermissions = Arrays.copyOf(expectedPermissions, expectedPermissions.length + 1);
+ assertFalse(ApkVerifier.requestedPermissionsEqual(badPermissions, apk.requestedPermissions));
+ badPermissions[badPermissions.length - 1] = "notarealpermission";
+ assertFalse(ApkVerifier.requestedPermissionsEqual(badPermissions, apk.requestedPermissions));
+
+ Uri uri = Uri.fromFile(extendedPermissionsApk);
+ ApkVerifier apkVerifier = new ApkVerifier(instrumentation.getContext(), uri, apk);
+ apkVerifier.verifyApk();
+ }
+
+ @NonNull
+ private RepoDetails getFromFile(File indexFile) throws IOException {
+ InputStream inputStream = null;
+ try {
+ inputStream = new FileInputStream(indexFile);
+ return RepoDetails.getFromFile(inputStream, Repo.PUSH_REQUEST_IGNORE);
+ } finally {
+ Utils.closeQuietly(inputStream);
+ }
+ }
}
diff --git a/app/src/main/java/org/fdroid/fdroid/RepoXMLHandler.java b/app/src/main/java/org/fdroid/fdroid/RepoXMLHandler.java
index 8735daebc..09a883591 100644
--- a/app/src/main/java/org/fdroid/fdroid/RepoXMLHandler.java
+++ b/app/src/main/java/org/fdroid/fdroid/RepoXMLHandler.java
@@ -19,6 +19,7 @@
package org.fdroid.fdroid;
+import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -32,7 +33,9 @@ import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.regex.Pattern;
/**
* Parses the index.xml into Java data structures.
@@ -57,7 +60,14 @@ public class RepoXMLHandler extends DefaultHandler {
private String repoDescription;
private String repoName;
- // the X.509 signing certificate stored in the header of index.xml
+ /**
+ * Set of requested permissions per package/APK
+ */
+ private final HashSet requestedPermissionsSet = new HashSet<>();
+
+ /**
+ * the X.509 signing certificate stored in the header of index.xml
+ */
private String repoSigningCert;
private final StringBuilder curchars = new StringBuilder();
@@ -89,6 +99,9 @@ public class RepoXMLHandler extends DefaultHandler {
if ("application".equals(localName) && curapp != null) {
onApplicationParsed();
} else if ("package".equals(localName) && curapk != null && curapp != null) {
+ int size = requestedPermissionsSet.size();
+ curapk.requestedPermissions = requestedPermissionsSet.toArray(new String[size]);
+ requestedPermissionsSet.clear();
apksList.add(curapk);
curapk = null;
} else if ("repo".equals(localName)) {
@@ -157,8 +170,8 @@ public class RepoXMLHandler extends DefaultHandler {
case ApkTable.Cols.ADDED_DATE:
curapk.added = Utils.parseDate(str, null);
break;
- case ApkTable.Cols.REQUESTED_PERMISSIONS:
- curapk.requestedPermissions = Utils.parseCommaSeparatedString(str);
+ case "permissions": // together with
+ * More info into index - size, permissions, features, sdk version
+ */
+ public static String fdroidToAndroidPermission(String permission) {
+ if (OLD_FDROID_PERMISSION.matcher(permission).matches()) {
+ return "android.permission." + permission;
+ }
+
+ return permission;
+ }
+
+ private void addRequestedPermission(String permission) {
+ requestedPermissionsSet.add(permission);
+ }
+
+ private void addCommaSeparatedPermissions(String permissions) {
+ String[] array = Utils.parseCommaSeparatedString(permissions);
+ if (array != null) {
+ for (String permission : array) {
+ requestedPermissionsSet.add(fdroidToAndroidPermission(permission));
+ }
+ }
+ }
+
+ private void removeRequestedPermission(String permission) {
+ requestedPermissionsSet.remove(permission);
+ }
+
private void onApplicationParsed() {
receiver.receiveApp(curapp, apksList);
curapp = null;
@@ -308,6 +357,24 @@ public class RepoXMLHandler extends DefaultHandler {
} else if ("hash".equals(localName) && curapk != null) {
currentApkHashType = attributes.getValue("", "type");
+ } else if ("uses-permission".equals(localName) && curapk != null) {
+ String maxSdkVersion = attributes.getValue("maxSdkVersion");
+ if (maxSdkVersion == null || Build.VERSION.SDK_INT <= Integer.valueOf(maxSdkVersion)) {
+ addRequestedPermission(attributes.getValue("name"));
+ } else {
+ removeRequestedPermission(attributes.getValue("name"));
+ }
+ } else if ("uses-permission-sdk-23".equals(localName) && curapk != null) {
+ String maxSdkVersion = attributes.getValue("maxSdkVersion");
+ if (Build.VERSION.SDK_INT >= 23 &&
+ (maxSdkVersion == null || Build.VERSION.SDK_INT <= Integer.valueOf(maxSdkVersion))) {
+ addRequestedPermission(attributes.getValue("name"));
+ } else {
+ removeRequestedPermission(attributes.getValue("name"));
+ }
+ } else if ("uses-feature".equals(localName) && curapk != null) {
+ System.out.println("TODO startElement " + uri + " " + localName + " " + qName);
+ // TODO
}
curchars.setLength(0);
}
diff --git a/app/src/main/java/org/fdroid/fdroid/data/Apk.java b/app/src/main/java/org/fdroid/fdroid/data/Apk.java
index 56f712aab..f43b0eeb0 100644
--- a/app/src/main/java/org/fdroid/fdroid/data/Apk.java
+++ b/app/src/main/java/org/fdroid/fdroid/data/Apk.java
@@ -7,11 +7,11 @@ import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
+import org.fdroid.fdroid.RepoXMLHandler;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Schema.ApkTable.Cols;
import java.io.File;
-import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
@@ -132,7 +132,7 @@ public class Apk extends ValueObject implements Comparable, Parcelable {
apkName = cursor.getString(i);
break;
case Cols.REQUESTED_PERMISSIONS:
- requestedPermissions = Utils.parseCommaSeparatedString(cursor.getString(i));
+ requestedPermissions = convertToRequestedPermissions(cursor.getString(i));
break;
case Cols.NATIVE_CODE:
nativecode = Utils.parseCommaSeparatedString(cursor.getString(i));
@@ -235,44 +235,6 @@ public class Apk extends ValueObject implements Comparable, Parcelable {
return new File(App.getObbDir(packageName), obbPatchFile);
}
- public ArrayList getFullPermissionList() {
- if (this.requestedPermissions == null) {
- return new ArrayList<>();
- }
-
- ArrayList permissionsFull = new ArrayList<>();
- for (String perm : this.requestedPermissions) {
- permissionsFull.add(fdroidToAndroidPermission(perm));
- }
- return permissionsFull;
- }
-
- public String[] getFullPermissionsArray() {
- ArrayList fullPermissions = getFullPermissionList();
- return fullPermissions.toArray(new String[fullPermissions.size()]);
- }
-
- public HashSet getFullPermissionsSet() {
- return new HashSet<>(getFullPermissionList());
- }
-
- /**
- * It appears that the default Android permissions in android.Manifest.permissions
- * are prefixed with "android.permission." and then the constant name.
- * FDroid just includes the constant name in the apk list, so we prefix it
- * with "android.permission."
- *
- * @see
- * More info into index - size, permissions, features, sdk version
- */
- private static String fdroidToAndroidPermission(String permission) {
- if (!permission.contains(".")) {
- return "android.permission." + permission;
- }
-
- return permission;
- }
-
@Override
public String toString() {
return toContentValues().toString();
@@ -393,4 +355,17 @@ public class Apk extends ValueObject implements Comparable, Parcelable {
return new Apk[size];
}
};
+
+ private String[] convertToRequestedPermissions(String permissionsFromDb) {
+ String[] array = Utils.parseCommaSeparatedString(permissionsFromDb);
+ if (array != null) {
+ HashSet requestedPermissionsSet = new HashSet<>();
+ for (String permission : array) {
+ requestedPermissionsSet.add(RepoXMLHandler.fdroidToAndroidPermission(permission));
+ }
+ return requestedPermissionsSet.toArray(new String[requestedPermissionsSet.size()]);
+ }
+ return null;
+ }
+
}
diff --git a/app/src/main/java/org/fdroid/fdroid/installer/ApkVerifier.java b/app/src/main/java/org/fdroid/fdroid/installer/ApkVerifier.java
index 76795df29..ea06fe437 100644
--- a/app/src/main/java/org/fdroid/fdroid/installer/ApkVerifier.java
+++ b/app/src/main/java/org/fdroid/fdroid/installer/ApkVerifier.java
@@ -80,18 +80,10 @@ class ApkVerifier {
}
// verify permissions, important for unattended installer
- HashSet localPermissions = getLocalPermissionsSet(localApkInfo);
- HashSet expectedPermissions = expectedApk.getFullPermissionsSet();
- Utils.debugLog(TAG, "localPermissions: " + localPermissions);
- Utils.debugLog(TAG, "expectedPermissions: " + expectedPermissions);
- // NOTE: Some permissions could have a maxSdkVersion < current sdk version
- // and are thus not parsed by pm.getPackageArchiveInfo().
- // Thus, containsAll() instead of equals() is used!
- // See also https://gitlab.com/fdroid/fdroidclient/issues/703
- if (!expectedPermissions.containsAll(localPermissions)) {
- throw new ApkPermissionUnequalException(
- "Permissions of the apk file are not a true subset of the permissions listed by the repo," +
- " i.e., some permissions have not been shown to the user!");
+ Utils.debugLog(TAG, "localPermissions: " + TextUtils.join("\n", localApkInfo.requestedPermissions));
+ Utils.debugLog(TAG, "expectedPermissions: " + TextUtils.join("\n", expectedApk.requestedPermissions));
+ if (!requestedPermissionsEqual(expectedApk.requestedPermissions, localApkInfo.requestedPermissions)) {
+ throw new ApkPermissionUnequalException("Permissions in APK and index.xml do not match!");
}
int localTargetSdkVersion = localApkInfo.applicationInfo.targetSdkVersion;
@@ -106,13 +98,24 @@ class ApkVerifier {
}
}
- private HashSet getLocalPermissionsSet(PackageInfo localApkInfo) {
- String[] localPermissions = localApkInfo.requestedPermissions;
- if (localPermissions == null) {
- return new HashSet<>();
+ /**
+ * Compares to sets of APK permissions to see if they are an exact match. The
+ * data format is {@link String} arrays but they are in effect sets. This is the
+ * same data format as {@link android.content.pm.PackageInfo#requestedPermissions}
+ */
+ public static boolean requestedPermissionsEqual(String[] expected, String[] actual) {
+ if (expected == null && actual == null) {
+ return true;
}
-
- return new HashSet<>(Arrays.asList(localApkInfo.requestedPermissions));
+ if (expected == null || actual == null) {
+ return false;
+ }
+ if (expected.length != actual.length) {
+ return false;
+ }
+ HashSet expectedSet = new HashSet<>(Arrays.asList(expected));
+ HashSet actualSet = new HashSet<>(Arrays.asList(actual));
+ return expectedSet.equals(actualSet);
}
public static class ApkVerificationException extends Exception {
diff --git a/app/src/main/java/org/fdroid/fdroid/privileged/views/AppDiff.java b/app/src/main/java/org/fdroid/fdroid/privileged/views/AppDiff.java
index f006a90a5..036a78533 100644
--- a/app/src/main/java/org/fdroid/fdroid/privileged/views/AppDiff.java
+++ b/app/src/main/java/org/fdroid/fdroid/privileged/views/AppDiff.java
@@ -39,7 +39,7 @@ public class AppDiff {
pkgInfo = new PackageInfo();
pkgInfo.packageName = apk.packageName;
pkgInfo.applicationInfo = new ApplicationInfo();
- pkgInfo.requestedPermissions = apk.getFullPermissionsArray();
+ pkgInfo.requestedPermissions = apk.requestedPermissions;
init();
}
diff --git a/app/src/test/java/org/fdroid/fdroid/updater/RepoXMLHandlerTest.java b/app/src/test/java/org/fdroid/fdroid/updater/RepoXMLHandlerTest.java
index 6f6eabc4e..b14ec574c 100644
--- a/app/src/test/java/org/fdroid/fdroid/updater/RepoXMLHandlerTest.java
+++ b/app/src/test/java/org/fdroid/fdroid/updater/RepoXMLHandlerTest.java
@@ -69,6 +69,17 @@ public class RepoXMLHandlerTest {
ShadowLog.stream = System.out;
}
+ @Test
+ public void testExtendedPerms() throws IOException {
+ Repo expectedRepo = new Repo();
+ expectedRepo.name = "F-Droid";
+ expectedRepo.signingCertificate = "3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef";
+ expectedRepo.description = "This is just a test of the extended permissions attributes.";
+ expectedRepo.timestamp = 1467169032;
+ RepoDetails actualDetails = getFromFile("extendedPerms.xml");
+ handlerTestSuite(expectedRepo, actualDetails, 2, 6, 14, 16);
+ }
+
@Test
public void testObbIndex() throws IOException {
writeResourceToObbDir("main.1101613.obb.main.twoversions.obb");
diff --git a/app/src/test/resources/extendedPerms.xml b/app/src/test/resources/extendedPerms.xml
new file mode 100644
index 000000000..4329e53c9
--- /dev/null
+++ b/app/src/test/resources/extendedPerms.xml
@@ -0,0 +1,195 @@
+
+
+
+
+ This is just a test of the extended permissions attributes.
+
+
+
+
+ at.bitfire.davdroid
+ 2013-10-13
+ 2016-09-21
+ DAVdroid
+ Contacts and Calendar sync
+ at.bitfire.davdroid.116.png
+
+
DAVdroid is a CalDAV/CardDAV synchronisation adapter for Android 4+ devices.
+
Use it with your own server (likeOwnCloud,Baïkal,
+ DAViCal
+ orradiCALe) or with a trusted hoster to keep your
+ contacts and events under your control.
+
+
Integrates natively in Android calendar/contact apps. See homepage for configuration
+ details, including info about self-signed certificates.
+
+
For a comparison of server software, see the
+ Debian wiki.
+
Asks you questions about a picture of a galaxy, with each question depending on the
+ previous question. This "Citizen Science" helps astronomers to analyze the huge
+ amount of images of galaxies provided, for instance, by the Hubble Space Telescope.
+