diff --git a/app/build.gradle b/app/build.gradle index b99c16ec1..c4a97632e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -189,6 +189,16 @@ android { } } + sourceSets { + test { + java.srcDirs += "$projectDir/src/testShared/java" + } + + androidTest { + java.srcDirs += "$projectDir/src/testShared/java" + } + } + lintOptions { checkReleaseBuilds false abortOnError true 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 d88380634..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.permissions = 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,12 +138,37 @@ 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(); apk.packageName = "org.fdroid.permissions.sdk14"; apk.targetSdkVersion = 14; - apk.permissions = new String[]{ + apk.requestedPermissions = new String[]{ "android.permission.AUTHENTICATE_ACCOUNTS", "android.permission.MANAGE_ACCOUNTS", "android.permission.READ_PROFILE", @@ -151,12 +203,13 @@ 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; - apk.permissions = new String[]{ + apk.requestedPermissions = new String[]{ "android.permission.AUTHENTICATE_ACCOUNTS", "android.permission.MANAGE_ACCOUNTS", "android.permission.READ_PROFILE", @@ -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(); } /** @@ -197,7 +243,7 @@ public class ApkVerifierTest { Apk apk = new Apk(); apk.packageName = "org.fdroid.permissions.sdk14"; apk.targetSdkVersion = 14; - apk.permissions = new String[]{ + apk.requestedPermissions = new String[]{ //"android.permission.AUTHENTICATE_ACCOUNTS", "android.permission.MANAGE_ACCOUNTS", "android.permission.READ_PROFILE", @@ -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 ec4cf5dcd..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.PERMISSIONS: - curapk.permissions = 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 406966308..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; @@ -36,8 +36,13 @@ public class Apk extends ValueObject implements Comparable, Parcelable { public String obbPatchFile; public String obbPatchFileSha256; public Date added; - public String[] permissions; // null if empty or - // unknown + /** + * The array of the names of the permissions that this APK requests. This is the + * same data as {@link android.content.pm.PackageInfo#requestedPermissions}. Note this + * does not mean that all these permissions have been granted, only requested. For + * example, a regular app can request a system permission, but it won't be granted it. + */ + public String[] requestedPermissions; public String[] features; // null if empty or unknown public String[] nativecode; // null if empty or unknown @@ -126,8 +131,8 @@ public class Apk extends ValueObject implements Comparable, Parcelable { case Cols.NAME: apkName = cursor.getString(i); break; - case Cols.PERMISSIONS: - permissions = Utils.parseCommaSeparatedString(cursor.getString(i)); + case Cols.REQUESTED_PERMISSIONS: + requestedPermissions = convertToRequestedPermissions(cursor.getString(i)); break; case Cols.NATIVE_CODE: nativecode = Utils.parseCommaSeparatedString(cursor.getString(i)); @@ -230,44 +235,6 @@ public class Apk extends ValueObject implements Comparable, Parcelable { return new File(App.getObbDir(packageName), obbPatchFile); } - public ArrayList getFullPermissionList() { - if (this.permissions == null) { - return new ArrayList<>(); - } - - ArrayList permissionsFull = new ArrayList<>(); - for (String perm : this.permissions) { - 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(); @@ -293,7 +260,7 @@ public class Apk extends ValueObject implements Comparable, Parcelable { values.put(Cols.OBB_PATCH_FILE, obbPatchFile); values.put(Cols.OBB_PATCH_FILE_SHA256, obbPatchFileSha256); values.put(Cols.ADDED_DATE, Utils.formatDate(added, "")); - values.put(Cols.PERMISSIONS, Utils.serializeCommaSeparatedString(permissions)); + values.put(Cols.REQUESTED_PERMISSIONS, Utils.serializeCommaSeparatedString(requestedPermissions)); values.put(Cols.FEATURES, Utils.serializeCommaSeparatedString(features)); values.put(Cols.NATIVE_CODE, Utils.serializeCommaSeparatedString(nativecode)); values.put(Cols.INCOMPATIBLE_REASONS, Utils.serializeCommaSeparatedString(incompatibleReasons)); @@ -332,7 +299,7 @@ public class Apk extends ValueObject implements Comparable, Parcelable { dest.writeString(this.obbPatchFile); dest.writeString(this.obbPatchFileSha256); dest.writeLong(this.added != null ? this.added.getTime() : -1); - dest.writeStringArray(this.permissions); + dest.writeStringArray(this.requestedPermissions); dest.writeStringArray(this.features); dest.writeStringArray(this.nativecode); dest.writeString(this.sig); @@ -363,7 +330,7 @@ public class Apk extends ValueObject implements Comparable, Parcelable { this.obbPatchFileSha256 = in.readString(); long tmpAdded = in.readLong(); this.added = tmpAdded == -1 ? null : new Date(tmpAdded); - this.permissions = in.createStringArray(); + this.requestedPermissions = in.createStringArray(); this.features = in.createStringArray(); this.nativecode = in.createStringArray(); this.sig = in.readString(); @@ -388,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/data/App.java b/app/src/main/java/org/fdroid/fdroid/data/App.java index e7d96ba1d..9bce69640 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/App.java +++ b/app/src/main/java/org/fdroid/fdroid/data/App.java @@ -372,7 +372,7 @@ public class App extends ValueObject implements Comparable, Parcelable { apk.targetSdkVersion = minTargetMax[1]; apk.maxSdkVersion = minTargetMax[2]; apk.packageName = this.packageName; - apk.permissions = packageInfo.requestedPermissions; + apk.requestedPermissions = packageInfo.requestedPermissions; apk.apkName = apk.packageName + "_" + apk.versionCode + ".apk"; apk.installedFile = apkFile; diff --git a/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java b/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java index aaee174b4..0a2125425 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java +++ b/app/src/main/java/org/fdroid/fdroid/data/DBHelper.java @@ -96,7 +96,7 @@ class DBHelper extends SQLiteOpenHelper { + ApkTable.Cols.OBB_MAIN_FILE_SHA256 + " string, " + ApkTable.Cols.OBB_PATCH_FILE + " string, " + ApkTable.Cols.OBB_PATCH_FILE_SHA256 + " string, " - + ApkTable.Cols.PERMISSIONS + " string, " + + ApkTable.Cols.REQUESTED_PERMISSIONS + " string, " + ApkTable.Cols.FEATURES + " string, " + ApkTable.Cols.NATIVE_CODE + " string, " + ApkTable.Cols.HASH_TYPE + " string, " @@ -504,7 +504,7 @@ class DBHelper extends SQLiteOpenHelper { + ApkTable.Cols.MIN_SDK_VERSION + " integer, " + ApkTable.Cols.TARGET_SDK_VERSION + " integer, " + ApkTable.Cols.MAX_SDK_VERSION + " integer, " - + ApkTable.Cols.PERMISSIONS + " string, " + + ApkTable.Cols.REQUESTED_PERMISSIONS + " string, " + ApkTable.Cols.FEATURES + " string, " + ApkTable.Cols.NATIVE_CODE + " string, " + ApkTable.Cols.HASH_TYPE + " string, " @@ -529,7 +529,7 @@ class DBHelper extends SQLiteOpenHelper { ApkTable.Cols.MIN_SDK_VERSION, ApkTable.Cols.TARGET_SDK_VERSION, ApkTable.Cols.MAX_SDK_VERSION, - ApkTable.Cols.PERMISSIONS, + ApkTable.Cols.REQUESTED_PERMISSIONS, ApkTable.Cols.FEATURES, ApkTable.Cols.NATIVE_CODE, ApkTable.Cols.HASH_TYPE, diff --git a/app/src/main/java/org/fdroid/fdroid/data/Schema.java b/app/src/main/java/org/fdroid/fdroid/data/Schema.java index b910c107f..8f39d62ed 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/Schema.java +++ b/app/src/main/java/org/fdroid/fdroid/data/Schema.java @@ -169,7 +169,7 @@ public interface Schema { String OBB_MAIN_FILE_SHA256 = "obbMainFileSha256"; String OBB_PATCH_FILE = "obbPatchFile"; String OBB_PATCH_FILE_SHA256 = "obbPatchFileSha256"; - String PERMISSIONS = "permissions"; + String REQUESTED_PERMISSIONS = "permissions"; String FEATURES = "features"; String NATIVE_CODE = "nativecode"; String HASH_TYPE = "hashType"; @@ -193,7 +193,7 @@ public interface Schema { APP_ID, VERSION_NAME, REPO_ID, HASH, VERSION_CODE, NAME, SIZE, SIGNATURE, SOURCE_NAME, MIN_SDK_VERSION, TARGET_SDK_VERSION, MAX_SDK_VERSION, OBB_MAIN_FILE, OBB_MAIN_FILE_SHA256, OBB_PATCH_FILE, OBB_PATCH_FILE_SHA256, - PERMISSIONS, FEATURES, NATIVE_CODE, HASH_TYPE, ADDED_DATE, + REQUESTED_PERMISSIONS, FEATURES, NATIVE_CODE, HASH_TYPE, ADDED_DATE, IS_COMPATIBLE, INCOMPATIBLE_REASONS, }; @@ -204,7 +204,7 @@ public interface Schema { _ID, APP_ID, Package.PACKAGE_NAME, VERSION_NAME, REPO_ID, HASH, VERSION_CODE, NAME, SIZE, SIGNATURE, SOURCE_NAME, MIN_SDK_VERSION, TARGET_SDK_VERSION, MAX_SDK_VERSION, OBB_MAIN_FILE, OBB_MAIN_FILE_SHA256, OBB_PATCH_FILE, OBB_PATCH_FILE_SHA256, - PERMISSIONS, FEATURES, NATIVE_CODE, HASH_TYPE, ADDED_DATE, + REQUESTED_PERMISSIONS, FEATURES, NATIVE_CODE, HASH_TYPE, ADDED_DATE, IS_COMPATIBLE, Repo.VERSION, Repo.ADDRESS, INCOMPATIBLE_REASONS, }; } 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/localrepo/LocalRepoManager.java b/app/src/main/java/org/fdroid/fdroid/localrepo/LocalRepoManager.java index 6e8db5e3d..7d66f08a9 100644 --- a/app/src/main/java/org/fdroid/fdroid/localrepo/LocalRepoManager.java +++ b/app/src/main/java/org/fdroid/fdroid/localrepo/LocalRepoManager.java @@ -436,10 +436,10 @@ public final class LocalRepoManager { private void tagPermissions(App app) throws IOException { serializer.startTag("", "permissions"); - if (app.installedApk.permissions != null) { + if (app.installedApk.requestedPermissions != null) { StringBuilder buff = new StringBuilder(); - for (String permission : app.installedApk.permissions) { + for (String permission : app.installedApk.requestedPermissions) { buff.append(permission.replace("android.permission.", "")); buff.append(','); } 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 193c8dc23..b14ec574c 100644 --- a/app/src/test/java/org/fdroid/fdroid/updater/RepoXMLHandlerTest.java +++ b/app/src/test/java/org/fdroid/fdroid/updater/RepoXMLHandlerTest.java @@ -28,23 +28,18 @@ import android.util.Log; import org.apache.commons.io.FileUtils; import org.fdroid.fdroid.BuildConfig; -import org.fdroid.fdroid.RepoXMLHandler; import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.App; import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.RepoPushRequest; -import org.fdroid.fdroid.mock.MockRepo; +import org.fdroid.fdroid.mock.RepoDetails; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricGradleTestRunner; import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowLog; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; -import org.xml.sax.XMLReader; -import java.io.BufferedInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -55,16 +50,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; // TODO: Use sdk=24 when Robolectric supports this @Config(constants = BuildConfig.class, sdk = 23) @@ -79,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"); @@ -835,41 +836,6 @@ public class RepoXMLHandlerTest { assertEquals(apks.size(), apkCount); } - private static class RepoDetails implements RepoXMLHandler.IndexReceiver { - - public String name; - public String description; - public String signingCert; - public int maxAge; - public int version; - public long timestamp; - - public List apks = new ArrayList<>(); - public List apps = new ArrayList<>(); - public List repoPushRequestList = new ArrayList<>(); - - @Override - public void receiveRepo(String name, String description, String signingCert, int maxage, int version, long timestamp) { - this.name = name; - this.description = description; - this.signingCert = signingCert; - this.maxAge = maxage; - this.version = version; - this.timestamp = timestamp; - } - - @Override - public void receiveApp(App app, List packages) { - apks.addAll(packages); - apps.add(app); - } - - @Override - public void receiveRepoPushRequest(RepoPushRequest repoPushRequest) { - repoPushRequestList.add(repoPushRequest); - } - } - @NonNull private RepoDetails getFromFile(String indexFilename) { return getFromFile(indexFilename, Repo.PUSH_REQUEST_IGNORE); @@ -877,28 +843,9 @@ public class RepoXMLHandlerTest { @NonNull private RepoDetails getFromFile(String indexFilename, int pushRequests) { - try { - SAXParserFactory factory = SAXParserFactory.newInstance(); - factory.setNamespaceAware(true); - SAXParser parser = factory.newSAXParser(); - XMLReader reader = parser.getXMLReader(); - RepoDetails repoDetails = new RepoDetails(); - MockRepo mockRepo = new MockRepo(100, pushRequests); - RepoXMLHandler handler = new RepoXMLHandler(mockRepo, repoDetails); - reader.setContentHandler(handler); - Log.i(TAG, "test file: " + getClass().getClassLoader().getResource(indexFilename)); - InputStream input = getClass().getClassLoader().getResourceAsStream(indexFilename); - InputSource is = new InputSource(new BufferedInputStream(input)); - reader.parse(is); - return repoDetails; - } catch (ParserConfigurationException | SAXException | IOException e) { - e.printStackTrace(); - fail(); - - // Satisfies the compiler, but fail() will always throw a runtime exception so we never - // reach this return statement. - return null; - } + Log.i(TAG, "test file: " + getClass().getClassLoader().getResource(indexFilename)); + InputStream inputStream = getClass().getClassLoader().getResourceAsStream(indexFilename); + return RepoDetails.getFromFile(inputStream, pushRequests); } private void writeResourceToObbDir(String assetName) throws IOException { 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 diff --git a/app/src/test/java/org/fdroid/fdroid/mock/MockRepo.java b/app/src/testShared/java/org/fdroid/fdroid/mock/MockRepo.java similarity index 100% rename from app/src/test/java/org/fdroid/fdroid/mock/MockRepo.java rename to app/src/testShared/java/org/fdroid/fdroid/mock/MockRepo.java diff --git a/app/src/testShared/java/org/fdroid/fdroid/mock/RepoDetails.java b/app/src/testShared/java/org/fdroid/fdroid/mock/RepoDetails.java new file mode 100644 index 000000000..4371bbaa4 --- /dev/null +++ b/app/src/testShared/java/org/fdroid/fdroid/mock/RepoDetails.java @@ -0,0 +1,85 @@ +package org.fdroid.fdroid.mock; + +import android.support.annotation.NonNull; + +import org.fdroid.fdroid.RepoXMLHandler; +import org.fdroid.fdroid.data.Apk; +import org.fdroid.fdroid.data.App; +import org.fdroid.fdroid.data.RepoPushRequest; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.XMLReader; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import static org.junit.Assert.fail; + +public class RepoDetails implements RepoXMLHandler.IndexReceiver { + public static final String TAG = "RepoDetails"; + + public String name; + public String description; + public String signingCert; + public int maxAge; + public int version; + public long timestamp; + + public List apks = new ArrayList<>(); + public List apps = new ArrayList<>(); + public List repoPushRequestList = new ArrayList<>(); + + @Override + public void receiveRepo(String name, String description, String signingCert, int maxage, int version, long timestamp) { + this.name = name; + this.description = description; + this.signingCert = signingCert; + this.maxAge = maxage; + this.version = version; + this.timestamp = timestamp; + } + + @Override + public void receiveApp(App app, List packages) { + apks.addAll(packages); + apps.add(app); + } + + @Override + public void receiveRepoPushRequest(RepoPushRequest repoPushRequest) { + repoPushRequestList.add(repoPushRequest); + } + + @NonNull + public static RepoDetails getFromFile(InputStream inputStream, int pushRequests) { + try { + SAXParserFactory factory = SAXParserFactory.newInstance(); + factory.setNamespaceAware(true); + SAXParser parser = factory.newSAXParser(); + XMLReader reader = parser.getXMLReader(); + RepoDetails repoDetails = new RepoDetails(); + MockRepo mockRepo = new MockRepo(100, pushRequests); + RepoXMLHandler handler = new RepoXMLHandler(mockRepo, repoDetails); + reader.setContentHandler(handler); + InputSource is = new InputSource(new BufferedInputStream(inputStream)); + reader.parse(is); + return repoDetails; + } catch (ParserConfigurationException | SAXException | IOException e) { + e.printStackTrace(); + fail(); + + // Satisfies the compiler, but fail() will always throw a runtime exception so we never + // reach this return statement. + return null; + } + } + +} +