support extended 'uses-permissions' tags in APKs
<uses-permissions/> tags can have min and max SDK to take effect. This is not supported currently, and it necessary especially with the privileged installer so it can properly represent the permissions that an APK is requesting. For example: <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" android:maxSdkVersion="22" /> <uses-permission-sdk-23 android:name="android.permission.CAMERA" /> <uses-permission-sdk-23 android:name="android.permission.CALL_PHONE" android:maxSdkVersion="23" />
This commit is contained in:
parent
2350b4e694
commit
6f0c9ff88a
125
app/src/androidTest/assets/extendedPerms.xml
Normal file
125
app/src/androidTest/assets/extendedPerms.xml
Normal file
@ -0,0 +1,125 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<fdroid>
|
||||
<repo name="F-Droid" icon="fdroid-icon.png" maxage="14"
|
||||
pubkey="3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef"
|
||||
timestamp="1467169032" url="http://f-droid.org/repo" version="16">
|
||||
<description>
|
||||
This is just a test of the extended permissions attributes.
|
||||
</description>
|
||||
</repo>
|
||||
|
||||
<application id="urzip.at.or.at.urzip">
|
||||
<id>at.bitfire.davdroid</id>
|
||||
<added>2013-10-13</added>
|
||||
<lastupdated>2016-06-26</lastupdated>
|
||||
<name>DAVdroid</name>
|
||||
<summary>Contacts and Calendar sync</summary>
|
||||
<icon>at.bitfire.davdroid.107.png</icon>
|
||||
<desc>apk generated from urzip to test extended permissions</desc>
|
||||
<license>GPLv3</license>
|
||||
<categories>Internet</categories>
|
||||
<category>Internet</category>
|
||||
<web>https://davdroid.bitfire.at/</web>
|
||||
<source>https://davdroid.bitfire.at/source/</source>
|
||||
<tracker>https://davdroid.bitfire.at/forums/</tracker>
|
||||
<changelog>https://gitlab.com/bitfireAT/davdroid/tags</changelog>
|
||||
<donate>https://davdroid.bitfire.at/donate/</donate>
|
||||
<bitcoin>1KSCy7RHztKuhW9fLLaUYqdwdC2iwbejZU</bitcoin>
|
||||
<flattr>2100160</flattr>
|
||||
<marketversion>1.1.1.2</marketversion>
|
||||
<marketvercode>107</marketvercode>
|
||||
<package>
|
||||
<version>1.3.2-FAKE</version>
|
||||
<versioncode>117</versioncode>
|
||||
<apkname>org.fdroid.extendedpermissionstest.apk</apkname>
|
||||
<srcname>at.bitfire.davdroid_116_src.tar.gz</srcname>
|
||||
<hash type="sha256">f1aa02257e99c167d2ea9b0e9525c3ce7c181fe2e7f4dd00b65dd81ed2e27a62
|
||||
</hash>
|
||||
<sig>03542175324d067b4c36582242f8aecc</sig>
|
||||
<size>3298864</size>
|
||||
<sdkver>14</sdkver>
|
||||
<targetSdkVersion>23</targetSdkVersion>
|
||||
<added>2016-09-22</added>
|
||||
<permissions>
|
||||
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
|
||||
</permissions>
|
||||
<uses-permission name="android.permission.GET_ACCOUNTS" maxSdkVersion="22" />
|
||||
<uses-permission name="android.permission.READ_EXTERNAL_STORAGE" maxSdkVersion="18" />
|
||||
<uses-permission name="android.permission.WRITE_EXTERNAL_STORAGE" maxSdkVersion="18" />
|
||||
<uses-permission name="android.permission.AUTHENTICATE_ACCOUNTS" maxSdkVersion="22" />
|
||||
<uses-permission name="android.permission.MANAGE_ACCOUNTS" maxSdkVersion="22" />
|
||||
<uses-permission-sdk-23 name="android.permission.CAMERA" />
|
||||
<uses-permission-sdk-23 name="android.permission.CALL_PHONE" maxSdkVersion="23" />
|
||||
</package>
|
||||
<package>
|
||||
<version>1.3.1-ose</version>
|
||||
<versioncode>116</versioncode>
|
||||
<apkname>at.bitfire.davdroid_116.apk</apkname>
|
||||
<srcname>at.bitfire.davdroid_116_src.tar.gz</srcname>
|
||||
<hash type="sha256">f1aa02257e99c167d2ea9b0e9525c3ce7c181fe2e7f4dd00b65dd81ed2e27a62
|
||||
</hash>
|
||||
<sig>03542175324d067b4c36582242f8aecc</sig>
|
||||
<size>3298864</size>
|
||||
<sdkver>14</sdkver>
|
||||
<targetSdkVersion>24</targetSdkVersion>
|
||||
<added>2016-09-21</added>
|
||||
<permissions>
|
||||
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
|
||||
</permissions>
|
||||
<uses-permission name="android.permission.GET_ACCOUNTS" maxSdkVersion="22" />
|
||||
<uses-permission name="android.permission.READ_EXTERNAL_STORAGE" maxSdkVersion="18" />
|
||||
<uses-permission name="android.permission.WRITE_EXTERNAL_STORAGE" maxSdkVersion="18" />
|
||||
<uses-permission name="android.permission.AUTHENTICATE_ACCOUNTS" maxSdkVersion="22" />
|
||||
<uses-permission name="android.permission.MANAGE_ACCOUNTS" maxSdkVersion="22" />
|
||||
</package>
|
||||
<package>
|
||||
<version>1.1.1.2</version>
|
||||
<versioncode>107</versioncode>
|
||||
<apkname>at.bitfire.davdroid_107.apk</apkname>
|
||||
<srcname>at.bitfire.davdroid_107_src.tar.gz</srcname>
|
||||
<hash type="sha256">9a616f2e97bf8cf012baf896f95667dea4e3ce3252b31c5715073638a9fcc3d4
|
||||
</hash>
|
||||
<sig>03542175324d067b4c36582242f8aecc</sig>
|
||||
<size>3134363</size>
|
||||
<sdkver>14</sdkver>
|
||||
<targetSdkVersion>23</targetSdkVersion>
|
||||
<added>2016-06-26</added>
|
||||
<permissions>
|
||||
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
|
||||
</permissions>
|
||||
</package>
|
||||
<package>
|
||||
<version>1.1.1.1</version>
|
||||
<versioncode>105</versioncode>
|
||||
<apkname>at.bitfire.davdroid_105.apk</apkname>
|
||||
<srcname>at.bitfire.davdroid_105_src.tar.gz</srcname>
|
||||
<hash type="sha256">4a0408c61536a1cc1028cea4273adbde2e57dfa2b12d93c3b52f4c3d095e2849
|
||||
</hash>
|
||||
<sig>03542175324d067b4c36582242f8aecc</sig>
|
||||
<size>3131567</size>
|
||||
<sdkver>14</sdkver>
|
||||
<targetSdkVersion>23</targetSdkVersion>
|
||||
<added>2016-06-24</added>
|
||||
<permissions>
|
||||
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
|
||||
</permissions>
|
||||
</package>
|
||||
<package>
|
||||
<version>1.1.1</version>
|
||||
<versioncode>104</versioncode>
|
||||
<apkname>at.bitfire.davdroid_104.apk</apkname>
|
||||
<srcname>at.bitfire.davdroid_104_src.tar.gz</srcname>
|
||||
<hash type="sha256">09ba34996177efe8b1498a93fe6521ab84efab3bccb0f42449116e80b59e5b56
|
||||
</hash>
|
||||
<sig>03542175324d067b4c36582242f8aecc</sig>
|
||||
<size>3131367</size>
|
||||
<sdkver>14</sdkver>
|
||||
<targetSdkVersion>23</targetSdkVersion>
|
||||
<added>2016-06-22</added>
|
||||
<permissions>
|
||||
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
|
||||
</permissions>
|
||||
</package>
|
||||
</application>
|
||||
|
||||
</fdroid>
|
Binary file not shown.
@ -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.
|
||||
* <p/>
|
||||
* <p>
|
||||
* 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<String> 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<String> 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<String> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<String> 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 <uses-permissions* makes ApkTable.Cols.REQUESTED_PERMISSIONS
|
||||
addCommaSeparatedPermissions(str);
|
||||
break;
|
||||
case ApkTable.Cols.FEATURES:
|
||||
curapk.features = Utils.parseCommaSeparatedString(str);
|
||||
@ -248,6 +261,42 @@ public class RepoXMLHandler extends DefaultHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private static final Pattern OLD_FDROID_PERMISSION = Pattern.compile("[A-Z_]+");
|
||||
|
||||
/**
|
||||
* 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 <a href="https://gitlab.com/fdroid/fdroidserver/blob/1afa8cfc/update.py#L91">
|
||||
* More info into index - size, permissions, features, sdk version</a>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
@ -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<Apk>, 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<Apk>, Parcelable {
|
||||
return new File(App.getObbDir(packageName), obbPatchFile);
|
||||
}
|
||||
|
||||
public ArrayList<String> getFullPermissionList() {
|
||||
if (this.requestedPermissions == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
ArrayList<String> permissionsFull = new ArrayList<>();
|
||||
for (String perm : this.requestedPermissions) {
|
||||
permissionsFull.add(fdroidToAndroidPermission(perm));
|
||||
}
|
||||
return permissionsFull;
|
||||
}
|
||||
|
||||
public String[] getFullPermissionsArray() {
|
||||
ArrayList<String> fullPermissions = getFullPermissionList();
|
||||
return fullPermissions.toArray(new String[fullPermissions.size()]);
|
||||
}
|
||||
|
||||
public HashSet<String> 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 <a href="https://gitlab.com/fdroid/fdroidserver/blob/1afa8cfc/update.py#L91">
|
||||
* More info into index - size, permissions, features, sdk version</a>
|
||||
*/
|
||||
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<Apk>, Parcelable {
|
||||
return new Apk[size];
|
||||
}
|
||||
};
|
||||
|
||||
private String[] convertToRequestedPermissions(String permissionsFromDb) {
|
||||
String[] array = Utils.parseCommaSeparatedString(permissionsFromDb);
|
||||
if (array != null) {
|
||||
HashSet<String> requestedPermissionsSet = new HashSet<>();
|
||||
for (String permission : array) {
|
||||
requestedPermissionsSet.add(RepoXMLHandler.fdroidToAndroidPermission(permission));
|
||||
}
|
||||
return requestedPermissionsSet.toArray(new String[requestedPermissionsSet.size()]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -80,18 +80,10 @@ class ApkVerifier {
|
||||
}
|
||||
|
||||
// verify permissions, important for unattended installer
|
||||
HashSet<String> localPermissions = getLocalPermissionsSet(localApkInfo);
|
||||
HashSet<String> 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<String> 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<String> expectedSet = new HashSet<>(Arrays.asList(expected));
|
||||
HashSet<String> actualSet = new HashSet<>(Arrays.asList(actual));
|
||||
return expectedSet.equals(actualSet);
|
||||
}
|
||||
|
||||
public static class ApkVerificationException extends Exception {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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");
|
||||
|
195
app/src/test/resources/extendedPerms.xml
Normal file
195
app/src/test/resources/extendedPerms.xml
Normal file
@ -0,0 +1,195 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<fdroid>
|
||||
<repo name="F-Droid" icon="fdroid-icon.png" maxage="14"
|
||||
pubkey="3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef"
|
||||
timestamp="1467169032" url="http://f-droid.org/repo" version="16">
|
||||
<description>
|
||||
This is just a test of the extended permissions attributes.
|
||||
</description>
|
||||
</repo>
|
||||
|
||||
<application id="at.bitfire.davdroid">
|
||||
<id>at.bitfire.davdroid</id>
|
||||
<added>2013-10-13</added>
|
||||
<lastupdated>2016-09-21</lastupdated>
|
||||
<name>DAVdroid</name>
|
||||
<summary>Contacts and Calendar sync</summary>
|
||||
<icon>at.bitfire.davdroid.116.png</icon>
|
||||
<desc>
|
||||
<p>DAVdroid is a CalDAV/CardDAV synchronisation adapter for Android 4+ devices.</p>
|
||||
<p>Use it with your own server (like<a href="https://owncloud.org/">OwnCloud</a>,<a
|
||||
href="http://baikal-server.com/">Baïkal</a>,
|
||||
<a href="http://www.davical.org/">DAViCal</a>
|
||||
or<a href="http://radicale.org/">radiCALe</a>) or with a trusted hoster to keep your
|
||||
contacts and events under your control.
|
||||
</p>
|
||||
<p>Integrates natively in Android calendar/contact apps. See homepage for configuration
|
||||
details, including info about self-signed certificates.
|
||||
</p>
|
||||
<p>For a comparison of server software, see the<a
|
||||
href="https://wiki.debian.org/Groupware">
|
||||
Debian wiki</a>.
|
||||
</p>
|
||||
</desc>
|
||||
<license>GPLv3</license>
|
||||
<categories>Internet</categories>
|
||||
<category>Internet</category>
|
||||
<web>https://davdroid.bitfire.at/</web>
|
||||
<source>https://davdroid.bitfire.at/source/</source>
|
||||
<tracker>https://davdroid.bitfire.at/forums/</tracker>
|
||||
<changelog>https://gitlab.com/bitfireAT/davdroid/tags</changelog>
|
||||
<donate>https://davdroid.bitfire.at/donate/</donate>
|
||||
<bitcoin>1KSCy7RHztKuhW9fLLaUYqdwdC2iwbejZU</bitcoin>
|
||||
<flattr>2100160</flattr>
|
||||
<marketversion>1.3.1-ose</marketversion>
|
||||
<marketvercode>116</marketvercode>
|
||||
<package>
|
||||
<version>1.3.1-ose</version>
|
||||
<versioncode>116</versioncode>
|
||||
<apkname>at.bitfire.davdroid_116.apk</apkname>
|
||||
<srcname>at.bitfire.davdroid_116_src.tar.gz</srcname>
|
||||
<hash type="sha256">f1aa02257e99c167d2ea9b0e9525c3ce7c181fe2e7f4dd00b65dd81ed2e27a62
|
||||
</hash>
|
||||
<sig>03542175324d067b4c36582242f8aecc</sig>
|
||||
<size>3298864</size>
|
||||
<sdkver>14</sdkver>
|
||||
<targetSdkVersion>24</targetSdkVersion>
|
||||
<added>2016-09-21</added>
|
||||
<permissions>
|
||||
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
|
||||
</permissions>
|
||||
<uses-permission name="android.permission.GET_ACCOUNTS" maxSdkVersion="22" />
|
||||
<uses-permission name="android.permission.READ_EXTERNAL_STORAGE" maxSdkVersion="18" />
|
||||
<uses-permission name="android.permission.WRITE_EXTERNAL_STORAGE" maxSdkVersion="18" />
|
||||
<uses-permission name="android.permission.AUTHENTICATE_ACCOUNTS" maxSdkVersion="22" />
|
||||
<uses-permission name="android.permission.MANAGE_ACCOUNTS" maxSdkVersion="22" />
|
||||
</package>
|
||||
<package>
|
||||
<version>1.3-ose</version>
|
||||
<versioncode>114</versioncode>
|
||||
<apkname>at.bitfire.davdroid_114.apk</apkname>
|
||||
<srcname>at.bitfire.davdroid_114_src.tar.gz</srcname>
|
||||
<hash type="sha256">aaf956539aad7400269997bf1a6689f191b592e70146ffe5484312f9375df9d9
|
||||
</hash>
|
||||
<sig>03542175324d067b4c36582242f8aecc</sig>
|
||||
<size>3295326</size>
|
||||
<sdkver>14</sdkver>
|
||||
<targetSdkVersion>24</targetSdkVersion>
|
||||
<added>2016-09-05</added>
|
||||
<permissions>
|
||||
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
|
||||
</permissions>
|
||||
<uses-permission name="android.permission.GET_ACCOUNTS" maxSdkVersion="22" />
|
||||
<uses-permission name="android.permission.READ_EXTERNAL_STORAGE" maxSdkVersion="18" />
|
||||
<uses-permission name="android.permission.WRITE_EXTERNAL_STORAGE" maxSdkVersion="18" />
|
||||
<uses-permission name="android.permission.AUTHENTICATE_ACCOUNTS" maxSdkVersion="22" />
|
||||
<uses-permission name="android.permission.MANAGE_ACCOUNTS" maxSdkVersion="22" />
|
||||
</package>
|
||||
<package>
|
||||
<version>1.2.3-ose</version>
|
||||
<versioncode>112</versioncode>
|
||||
<apkname>at.bitfire.davdroid_112.apk</apkname>
|
||||
<srcname>at.bitfire.davdroid_112_src.tar.gz</srcname>
|
||||
<hash type="sha256">045480c571f4dfabb23a5efb803b594c432c20ab33b6eb575b4476a8df22bb05
|
||||
</hash>
|
||||
<sig>03542175324d067b4c36582242f8aecc</sig>
|
||||
<size>3172611</size>
|
||||
<sdkver>14</sdkver>
|
||||
<targetSdkVersion>24</targetSdkVersion>
|
||||
<added>2016-08-12</added>
|
||||
<permissions>
|
||||
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
|
||||
</permissions>
|
||||
<uses-permission name="android.permission.READ_EXTERNAL_STORAGE" maxSdkVersion="18" />
|
||||
<uses-permission name="android.permission.WRITE_EXTERNAL_STORAGE" maxSdkVersion="18" />
|
||||
<uses-permission name="android.permission.AUTHENTICATE_ACCOUNTS" maxSdkVersion="22" />
|
||||
</package>
|
||||
</application>
|
||||
<application id="com.murrayc.galaxyzoo.app">
|
||||
<id>com.murrayc.galaxyzoo.app</id>
|
||||
<added>2014-11-23</added>
|
||||
<lastupdated>2016-09-02</lastupdated>
|
||||
<name>Galaxy Zoo</name>
|
||||
<summary>Help to classify galaxies</summary>
|
||||
<icon>com.murrayc.galaxyzoo.app.64.png</icon>
|
||||
<desc>
|
||||
<p>Classify
|
||||
<a href="http://www.galaxyzoo.org/">Galaxy Zoo</a>
|
||||
subjects. Official approved by the<a href="https://www.zooniverse.org/">Zooniverse
|
||||
project</a>.
|
||||
</p>
|
||||
<p>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.
|
||||
</p>
|
||||
</desc>
|
||||
<license>GPLv3</license>
|
||||
<categories>Science & Education</categories>
|
||||
<category>Science & Education</category>
|
||||
<web />
|
||||
<source>https://github.com/murraycu/android-galaxyzoo</source>
|
||||
<tracker>https://github.com/murraycu/android-galaxyzoo/issues</tracker>
|
||||
<marketversion>1.64</marketversion>
|
||||
<marketvercode>64</marketvercode>
|
||||
<package>
|
||||
<version>1.64</version>
|
||||
<versioncode>64</versioncode>
|
||||
<apkname>com.murrayc.galaxyzoo.app_64.apk</apkname>
|
||||
<srcname>com.murrayc.galaxyzoo.app_64_src.tar.gz</srcname>
|
||||
<hash type="sha256">6f10487c8ef84078232aafe115228c6252ef982a228666454005b8d62c27fe42
|
||||
</hash>
|
||||
<sig>f1a84be1ce965e270f64318cfdb41861</sig>
|
||||
<size>4095584</size>
|
||||
<sdkver>11</sdkver>
|
||||
<targetSdkVersion>24</targetSdkVersion>
|
||||
<added>2016-09-02</added>
|
||||
<permissions>
|
||||
READ_EXTERNAL_STORAGE,WRITE_SYNC_SETTINGS,ACCESS_NETWORK_STATE,WRITE_EXTERNAL_STORAGE,USE_CREDENTIALS,READ_SYNC_SETTINGS,MANAGE_ACCOUNTS,INTERNET,AUTHENTICATE_ACCOUNTS,GET_ACCOUNTS
|
||||
</permissions>
|
||||
<uses-permission name="android.permission.USE_CREDENTIALS" maxSdkVersion="22" />
|
||||
<uses-permission name="android.permission.GET_ACCOUNTS" maxSdkVersion="22" />
|
||||
<uses-permission name="android.permission.AUTHENTICATE_ACCOUNTS" maxSdkVersion="22" />
|
||||
<uses-permission name="android.permission.MANAGE_ACCOUNTS" maxSdkVersion="22" />
|
||||
</package>
|
||||
<package>
|
||||
<version>1.61</version>
|
||||
<versioncode>61</versioncode>
|
||||
<apkname>com.murrayc.galaxyzoo.app_61.apk</apkname>
|
||||
<srcname>com.murrayc.galaxyzoo.app_61_src.tar.gz</srcname>
|
||||
<hash type="sha256">c35bc15885d57a2ddfcb2c702c9147b63b63a9765a6eeacf98d6a1534f6f8ff2
|
||||
</hash>
|
||||
<sig>f1a84be1ce965e270f64318cfdb41861</sig>
|
||||
<size>4588315</size>
|
||||
<sdkver>11</sdkver>
|
||||
<targetSdkVersion>24</targetSdkVersion>
|
||||
<added>2016-08-26</added>
|
||||
<permissions>
|
||||
READ_EXTERNAL_STORAGE,WRITE_SYNC_SETTINGS,ACCESS_NETWORK_STATE,WRITE_EXTERNAL_STORAGE,USE_CREDENTIALS,READ_SYNC_SETTINGS,MANAGE_ACCOUNTS,INTERNET,AUTHENTICATE_ACCOUNTS,GET_ACCOUNTS
|
||||
</permissions>
|
||||
<uses-permission name="android.permission.USE_CREDENTIALS" maxSdkVersion="22" />
|
||||
<uses-permission name="android.permission.GET_ACCOUNTS" maxSdkVersion="22" />
|
||||
<uses-permission name="android.permission.AUTHENTICATE_ACCOUNTS" maxSdkVersion="22" />
|
||||
<uses-permission name="android.permission.MANAGE_ACCOUNTS" maxSdkVersion="22" />
|
||||
</package>
|
||||
<package>
|
||||
<version>1.59</version>
|
||||
<versioncode>59</versioncode>
|
||||
<apkname>com.murrayc.galaxyzoo.app_59.apk</apkname>
|
||||
<srcname>com.murrayc.galaxyzoo.app_59_src.tar.gz</srcname>
|
||||
<hash type="sha256">719d1440dd2f16ad29b45edba5b43262f22ef68cee0c6b478e40fe8eb0e53227
|
||||
</hash>
|
||||
<sig>f1a84be1ce965e270f64318cfdb41861</sig>
|
||||
<size>4116175</size>
|
||||
<sdkver>11</sdkver>
|
||||
<targetSdkVersion>24</targetSdkVersion>
|
||||
<added>2016-08-25</added>
|
||||
<permissions>
|
||||
READ_EXTERNAL_STORAGE,WRITE_SYNC_SETTINGS,ACCESS_NETWORK_STATE,WRITE_EXTERNAL_STORAGE,USE_CREDENTIALS,READ_SYNC_SETTINGS,MANAGE_ACCOUNTS,INTERNET,AUTHENTICATE_ACCOUNTS,GET_ACCOUNTS
|
||||
</permissions>
|
||||
<uses-permission name="android.permission.USE_CREDENTIALS" maxSdkVersion="22" />
|
||||
<uses-permission name="android.permission.GET_ACCOUNTS" maxSdkVersion="22" />
|
||||
<uses-permission name="android.permission.AUTHENTICATE_ACCOUNTS" maxSdkVersion="22" />
|
||||
<uses-permission name="android.permission.MANAGE_ACCOUNTS" maxSdkVersion="22" />
|
||||
</package>
|
||||
</application>
|
||||
</fdroid>
|
Loading…
x
Reference in New Issue
Block a user