chiark / gitweb /
Merge branch 'no_sleep' into 'master'
[fdroidserver.git] / tests / index.TestCase
1 #!/usr/bin/env python3
2
3 import inspect
4 import logging
5 import optparse
6 import os
7 import sys
8 import unittest
9 import zipfile
10 from unittest.mock import patch
11 import requests
12 import tempfile
13 import json
14 import shutil
15
16 localmodule = os.path.realpath(
17     os.path.join(os.path.dirname(inspect.getfile(inspect.currentframe())), '..'))
18 print('localmodule: ' + localmodule)
19 if localmodule not in sys.path:
20     sys.path.insert(0, localmodule)
21
22 import fdroidserver.common
23 import fdroidserver.index
24 import fdroidserver.signindex
25 import fdroidserver.publish
26 from testcommon import TmpCwd
27
28
29 GP_FINGERPRINT = 'B7C2EEFD8DAC7806AF67DFCD92EB18126BC08312A7F2D6F3862E46013C7A6135'
30
31
32 class IndexTest(unittest.TestCase):
33
34     def setUp(self):
35         logging.basicConfig(level=logging.DEBUG)
36         self.basedir = os.path.join(localmodule, 'tests')
37         self.tmpdir = os.path.abspath(os.path.join(self.basedir, '..', '.testfiles'))
38         if not os.path.exists(self.tmpdir):
39             os.makedirs(self.tmpdir)
40         os.chdir(self.basedir)
41
42         fdroidserver.common.config = None
43         config = fdroidserver.common.read_config(fdroidserver.common.options)
44         config['jarsigner'] = fdroidserver.common.find_sdk_tools_cmd('jarsigner')
45         fdroidserver.common.config = config
46         fdroidserver.signindex.config = config
47
48     def test_verify_jar_signature_succeeds(self):
49         source_dir = os.path.join(self.basedir, 'signindex')
50         for f in ('testy.jar', 'guardianproject.jar'):
51             testfile = os.path.join(source_dir, f)
52             fdroidserver.common.verify_jar_signature(testfile)
53
54     def test_verify_jar_signature_fails(self):
55         source_dir = os.path.join(self.basedir, 'signindex')
56         testfile = os.path.join(source_dir, 'unsigned.jar')
57         with self.assertRaises(fdroidserver.index.VerificationException):
58             fdroidserver.common.verify_jar_signature(testfile)
59
60     def test_get_public_key_from_jar_succeeds(self):
61         source_dir = os.path.join(self.basedir, 'signindex')
62         for f in ('testy.jar', 'guardianproject.jar'):
63             testfile = os.path.join(source_dir, f)
64             jar = zipfile.ZipFile(testfile)
65             _, fingerprint = fdroidserver.index.get_public_key_from_jar(jar)
66             # comparing fingerprints should be sufficient
67             if f == 'testy.jar':
68                 self.assertTrue(fingerprint ==
69                                 '818E469465F96B704E27BE2FEE4C63AB' +
70                                 '9F83DDF30E7A34C7371A4728D83B0BC1')
71             if f == 'guardianproject.jar':
72                 self.assertTrue(fingerprint == GP_FINGERPRINT)
73
74     def test_get_public_key_from_jar_fails(self):
75         source_dir = os.path.join(self.basedir, 'signindex')
76         testfile = os.path.join(source_dir, 'unsigned.jar')
77         jar = zipfile.ZipFile(testfile)
78         with self.assertRaises(fdroidserver.index.VerificationException):
79             fdroidserver.index.get_public_key_from_jar(jar)
80
81     def test_download_repo_index_no_fingerprint(self):
82         with self.assertRaises(fdroidserver.index.VerificationException):
83             fdroidserver.index.download_repo_index("http://example.org")
84
85     def test_download_repo_index_no_jar(self):
86         with self.assertRaises(requests.exceptions.HTTPError):
87             fdroidserver.index.download_repo_index("http://example.org?fingerprint=nope")
88
89     @patch('requests.head')
90     def test_download_repo_index_same_etag(self, head):
91         url = 'http://example.org?fingerprint=test'
92         etag = '"4de5-54d840ce95cb9"'
93
94         head.return_value.headers = {'ETag': etag}
95         index, new_etag = fdroidserver.index.download_repo_index(url, etag=etag)
96
97         self.assertIsNone(index)
98         self.assertEqual(etag, new_etag)
99
100     @patch('requests.get')
101     @patch('requests.head')
102     def test_download_repo_index_new_etag(self, head, get):
103         url = 'http://example.org?fingerprint=' + GP_FINGERPRINT
104         etag = '"4de5-54d840ce95cb9"'
105
106         # fake HTTP answers
107         head.return_value.headers = {'ETag': 'new_etag'}
108         get.return_value.headers = {'ETag': 'new_etag'}
109         get.return_value.status_code = 200
110         testfile = os.path.join(os.path.dirname(__file__), 'signindex', 'guardianproject-v1.jar')
111         with open(testfile, 'rb') as file:
112             get.return_value.content = file.read()
113
114         index, new_etag = fdroidserver.index.download_repo_index(url, etag=etag)
115
116         # assert that the index was retrieved properly
117         self.assertEqual('Guardian Project Official Releases', index['repo']['name'])
118         self.assertEqual(GP_FINGERPRINT, index['repo']['fingerprint'])
119         self.assertTrue(len(index['repo']['pubkey']) > 500)
120         self.assertEqual(10, len(index['apps']))
121         self.assertEqual(10, len(index['packages']))
122         self.assertEqual('new_etag', new_etag)
123
124     def test_v1_sort_packages(self):
125
126         i = [{'packageName': 'org.smssecure.smssecure',
127               'apkName': 'org.smssecure.smssecure_134.apk',
128               'signer': 'b33a601a9da97c82e6eb121eb6b90adab561f396602ec4dc8b0019fb587e2af6',
129               'versionCode': 134},
130              {'packageName': 'org.smssecure.smssecure',
131               'apkName': 'org.smssecure.smssecure_134_b30bb97.apk',
132               'signer': 'b30bb971af0d134866e158ec748fcd553df97c150f58b0a963190bbafbeb0868',
133               'versionCode': 134},
134              {'packageName': 'b075b32b4ef1e8a869e00edb136bd48e34a0382b85ced8628f164d1199584e4e'},
135              {'packageName': '43af70d1aca437c2f9974c4634cc5abe45bdc4d5d71529ac4e553488d3bb3ff6'},
136              {'packageName': 'org.smssecure.smssecure',
137               'apkName': 'org.smssecure.smssecure_135_b30bb97.apk',
138               'signer': 'b30bb971af0d134866e158ec748fcd553df97c150f58b0a963190bbafbeb0868',
139               'versionCode': 135},
140              {'packageName': 'org.smssecure.smssecure',
141               'apkName': 'org.smssecure.smssecure_135.apk',
142               'signer': 'b33a601a9da97c82e6eb121eb6b90adab561f396602ec4dc8b0019fb587e2af6',
143               'versionCode': 135},
144              {'packageName': 'org.smssecure.smssecure',
145               'apkName': 'org.smssecure.smssecure_133.apk',
146               'signer': 'b33a601a9da97c82e6eb121eb6b90adab561f396602ec4dc8b0019fb587e2af6',
147               'versionCode': 133},
148              {'packageName': 'org.smssecure.smssecure',
149               'apkName': 'smssecure-weird-version.apk',
150               'signer': '99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff',
151               'versionCode': 133},
152              {'packageName': 'org.smssecure.smssecure',
153               'apkName': 'smssecure-custom.apk',
154               'signer': '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
155               'versionCode': 133},
156              {'packageName': 'org.smssecure.smssecure',
157               'apkName': 'smssecure-new-custom.apk',
158               'signer': '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
159               'versionCode': 135}]
160
161         o = [{'packageName': '43af70d1aca437c2f9974c4634cc5abe45bdc4d5d71529ac4e553488d3bb3ff6'},
162              {'packageName': 'b075b32b4ef1e8a869e00edb136bd48e34a0382b85ced8628f164d1199584e4e'},
163              # app test data
164              # # packages with reproducible developer signature
165              {'packageName': 'org.smssecure.smssecure',
166               'apkName': 'org.smssecure.smssecure_135_b30bb97.apk',
167               'signer': 'b30bb971af0d134866e158ec748fcd553df97c150f58b0a963190bbafbeb0868',
168               'versionCode': 135},
169              {'packageName': 'org.smssecure.smssecure',
170               'apkName': 'org.smssecure.smssecure_134_b30bb97.apk',
171               'signer': 'b30bb971af0d134866e158ec748fcd553df97c150f58b0a963190bbafbeb0868',
172               'versionCode': 134},
173              # # packages build and signed by fdroid
174              {'packageName': 'org.smssecure.smssecure',
175               'apkName': 'org.smssecure.smssecure_135.apk',
176               'signer': 'b33a601a9da97c82e6eb121eb6b90adab561f396602ec4dc8b0019fb587e2af6',
177               'versionCode': 135},
178              {'packageName': 'org.smssecure.smssecure',
179               'apkName': 'org.smssecure.smssecure_134.apk',
180               'signer': 'b33a601a9da97c82e6eb121eb6b90adab561f396602ec4dc8b0019fb587e2af6',
181               'versionCode': 134},
182              {'packageName': 'org.smssecure.smssecure',
183               'apkName': 'org.smssecure.smssecure_133.apk',
184               'signer': 'b33a601a9da97c82e6eb121eb6b90adab561f396602ec4dc8b0019fb587e2af6',
185               'versionCode': 133},
186              # # packages signed with unkown keys
187              {'packageName': 'org.smssecure.smssecure',
188               'apkName': 'smssecure-new-custom.apk',
189               'signer': '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
190               'versionCode': 135},
191              {'packageName': 'org.smssecure.smssecure',
192               'apkName': 'smssecure-custom.apk',
193               'signer': '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
194               'versionCode': 133},
195              {'packageName': 'org.smssecure.smssecure',
196               'apkName': 'smssecure-weird-version.apk',
197               'signer': '99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff99ff',
198               'versionCode': 133}]
199
200         fdroidserver.common.config = {}
201         fdroidserver.common.fill_config_defaults(fdroidserver.common.config)
202         fdroidserver.publish.config = fdroidserver.common.config
203         fdroidserver.publish.config['keystorepass'] = '123456'
204         fdroidserver.publish.config['keypass'] = '123456'
205         fdroidserver.publish.config['keystore'] = os.path.join(os.getcwd(),
206                                                                'dummy-keystore.jks')
207         fdroidserver.publish.config['repo_keyalias'] = 'repokey'
208
209         testsmetadir = os.path.join(os.getcwd(), 'metadata')
210         with tempfile.TemporaryDirectory() as tmpdir, TmpCwd(tmpdir):
211             shutil.copytree(testsmetadir, 'metadata')
212             sigkeyfps = {
213                 "org.smssecure.smssecure": {
214                     "signer": "b33a601a9da97c82e6eb121eb6b90adab561f396602ec4dc8b0019fb587e2af6"
215                 }
216             }
217             os.makedirs('stats')
218             jarfile = 'stats/publishsigkeys.jar'
219             with zipfile.ZipFile(jarfile, 'w', zipfile.ZIP_DEFLATED) as jar:
220                 jar.writestr('publishsigkeys.json', json.dumps(sigkeyfps))
221             fdroidserver.publish.sign_sig_key_fingerprint_list(jarfile)
222             with open('config.py', 'w'):
223                 pass
224
225             fdroidserver.index.v1_sort_packages(
226                 i, 'repo', fdroidserver.common.load_stats_fdroid_signing_key_fingerprints())
227             self.maxDiff = None
228             self.assertEqual(json.dumps(i, indent=2), json.dumps(o, indent=2))
229
230
231 if __name__ == "__main__":
232     parser = optparse.OptionParser()
233     parser.add_option("-v", "--verbose", action="store_true", default=False,
234                       help="Spew out even more information than normal")
235     (fdroidserver.common.options, args) = parser.parse_args(['--verbose'])
236
237     newSuite = unittest.TestSuite()
238     newSuite.addTest(unittest.makeSuite(IndexTest))
239     unittest.main(failfast=False)