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. +

+
+ 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.3.1-ose + 116 + + 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.3-ose + 114 + at.bitfire.davdroid_114.apk + at.bitfire.davdroid_114_src.tar.gz + aaf956539aad7400269997bf1a6689f191b592e70146ffe5484312f9375df9d9 + + 03542175324d067b4c36582242f8aecc + 3295326 + 14 + 24 + 2016-09-05 + + 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.2.3-ose + 112 + at.bitfire.davdroid_112.apk + at.bitfire.davdroid_112_src.tar.gz + 045480c571f4dfabb23a5efb803b594c432c20ab33b6eb575b4476a8df22bb05 + + 03542175324d067b4c36582242f8aecc + 3172611 + 14 + 24 + 2016-08-12 + + 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 + + + + + + + + com.murrayc.galaxyzoo.app + 2014-11-23 + 2016-09-02 + Galaxy Zoo + Help to classify galaxies + com.murrayc.galaxyzoo.app.64.png + +

Classify + Galaxy Zoo + subjects. Official approved by theZooniverse + project. +

+

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. +

+
+ GPLv3 + Science & Education + Science & Education + + https://github.com/murraycu/android-galaxyzoo + https://github.com/murraycu/android-galaxyzoo/issues + 1.64 + 64 + + 1.64 + 64 + com.murrayc.galaxyzoo.app_64.apk + com.murrayc.galaxyzoo.app_64_src.tar.gz + 6f10487c8ef84078232aafe115228c6252ef982a228666454005b8d62c27fe42 + + f1a84be1ce965e270f64318cfdb41861 + 4095584 + 11 + 24 + 2016-09-02 + + READ_EXTERNAL_STORAGE,WRITE_SYNC_SETTINGS,ACCESS_NETWORK_STATE,WRITE_EXTERNAL_STORAGE,USE_CREDENTIALS,READ_SYNC_SETTINGS,MANAGE_ACCOUNTS,INTERNET,AUTHENTICATE_ACCOUNTS,GET_ACCOUNTS + + + + + + + + 1.61 + 61 + com.murrayc.galaxyzoo.app_61.apk + com.murrayc.galaxyzoo.app_61_src.tar.gz + c35bc15885d57a2ddfcb2c702c9147b63b63a9765a6eeacf98d6a1534f6f8ff2 + + f1a84be1ce965e270f64318cfdb41861 + 4588315 + 11 + 24 + 2016-08-26 + + READ_EXTERNAL_STORAGE,WRITE_SYNC_SETTINGS,ACCESS_NETWORK_STATE,WRITE_EXTERNAL_STORAGE,USE_CREDENTIALS,READ_SYNC_SETTINGS,MANAGE_ACCOUNTS,INTERNET,AUTHENTICATE_ACCOUNTS,GET_ACCOUNTS + + + + + + + + 1.59 + 59 + com.murrayc.galaxyzoo.app_59.apk + com.murrayc.galaxyzoo.app_59_src.tar.gz + 719d1440dd2f16ad29b45edba5b43262f22ef68cee0c6b478e40fe8eb0e53227 + + f1a84be1ce965e270f64318cfdb41861 + 4116175 + 11 + 24 + 2016-08-25 + + READ_EXTERNAL_STORAGE,WRITE_SYNC_SETTINGS,ACCESS_NETWORK_STATE,WRITE_EXTERNAL_STORAGE,USE_CREDENTIALS,READ_SYNC_SETTINGS,MANAGE_ACCOUNTS,INTERNET,AUTHENTICATE_ACCOUNTS,GET_ACCOUNTS + + + + + + +
+ \ No newline at end of file