def setUp(self): app.config['DEBUG'] = True app.config['WHITELISTED_DOMAINS'] = ('a.com', ) dbo.setDb('sqlite:///:memory:') dbo.create() self.client = app.test_client() self.view = ClientRequestView()
import logging log = logging.getLogger(__name__) from flask import Flask, make_response, request from auslib import version from auslib.AUS import AUS3 app = Flask(__name__) AUS = AUS3() from auslib.web.views.client import ClientRequestView @app.errorhandler(404) def fourohfour(error): """We don't return 404s in AUS. Instead, we return empty XML files""" response = make_response('<?xml version="1.0"?>\n<updates>\n</updates>') response.mimetype = 'text/xml' return response @app.errorhandler(500) def isa(error): log.error("Caught ISE 500 error.") log.debug("Balrog version is: %s", version) log.debug("Request path is: %s", request.path) log.debug("Request environment is: %s", request.environ) log.debug("Request headers are: %s", request.headers) return error app.add_url_rule('/update/<int:queryVersion>/<product>/<version>/<buildID>/<buildTarget>/<locale>/<channel>/<osVersion>/<distribution>/<distVersion>/update.xml', view_func=ClientRequestView.as_view('clientrequest'))
'/update/4/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/%MOZ_VERSION%' '/update.xml') @app.route( '/update/5/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/%IMEI%/update.xml' ) @app.route( '/update/6/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%SYSTEM_CAPABILITIES%/%DISTRIBUTION%' '/%DISTRIBUTION_VERSION%/update.xml') def unsubstituted_url_variables(): abort(404) # The "main" routes. 99% of requests will come in through these. app.add_url_rule( "/update/1/<product>/<version>/<buildID>/<buildTarget>/<locale>/<channel>/update.xml", view_func=ClientRequestView.as_view("clientrequest1"), # Underlying code depends on osVersion being set. Since this route only # exists to support ancient queries, and all newer versions have osVersion # in them it's easier to set this here than make the all of the underlying # code support queries without it. defaults={ "queryVersion": 2, "osVersion": "" }, ) app.add_url_rule( '/update/2/<product>/<version>/<buildID>/<buildTarget>/<locale>/<channel>/<osVersion>/update.xml', view_func=ClientRequestView.as_view('clientrequest2'), defaults={'queryVersion': 2}, ) app.add_url_rule(
def setUp(self): self.cef_fd, self.cef_file = mkstemp() app.config['DEBUG'] = True app.config['SPECIAL_FORCE_HOSTS'] = ('http://a.com', ) app.config['WHITELISTED_DOMAINS'] = ('a.com', 'boring.com') dbo.setDb('sqlite:///:memory:') dbo.create() dbo.setDomainWhitelist(('a.com', 'boring.com')) self.client = app.test_client() self.view = ClientRequestView() auslib.log.cef_config = auslib.log.get_cef_config(self.cef_file) dbo.rules.t.insert().execute(backgroundRate=100, mapping='b', update_type='minor', product='b', data_version=1) dbo.releases.t.insert().execute(name='b', product='b', version='1.0', data_version=1, data=""" { "name": "b", "schema_version": 1, "appv": "1.0", "extv": "1.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "2", "locales": { "l": { "complete": { "filesize": "3", "from": "*", "hashValue": "4", "fileUrl": "http://a.com/z" } }, "xh": { "complete": { "filesize": "5", "from": "*", "hashValue": "6", "fileUrl": "http://a.com/x" } } } } } } """) dbo.rules.t.insert().execute(backgroundRate=100, mapping='c', update_type='minor', product='c', distribution='default', data_version=1) dbo.releases.t.insert().execute(name='c', product='c', version='10.0', data_version=1, data=""" { "name": "c", "schema_version": 1, "appv": "10.0", "extv": "10.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "11", "locales": { "l": { "complete": { "filesize": "12", "from": "*", "hashValue": "13", "fileUrl": "http://a.com/y" } } } } } } """) dbo.rules.t.insert().execute(backgroundRate=100, mapping='d', update_type='minor', product='d', data_version=1) dbo.releases.t.insert().execute(name='d', product='d', version='20.0', data_version=1, data=""" { "name": "d", "schema_version": 1, "appv": "20.0", "extv": "20.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "21", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://evil.com/y" } } } } } } """) dbo.rules.t.insert().execute(backgroundRate=100, mapping='e', update_type='minor', product='e', data_version=1) dbo.releases.t.insert().execute(name='e', product='e', version='22.0', data_version=1, data=""" { "name": "e", "schema_version": 1, "hashFunction": "sha512", "platforms": { "p": { "buildID": "25", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://a.com/y" } } } } } } """)
a 200 response with no updates is returned, because that's what the client expects. See bugs 885173 and 1069454 for additional background.""" if not isinstance(error, BadDataError): if sentry.client: sentry.captureException() log.debug('Hit exception, sending an empty response') response = make_response('<?xml version="1.0"?>\n<updates>\n</updates>') response.mimetype = 'text/xml' return response @app.route('/robots.txt') def robots(): return send_from_directory(app.static_folder, "robots.txt") app.add_url_rule( '/update/2/<product>/<version>/<buildID>/<buildTarget>/<locale>/<channel>/<osVersion>/update.xml', view_func=ClientRequestView.as_view('clientrequest2'), defaults={'queryVersion': 2}, ) app.add_url_rule( '/update/3/<product>/<version>/<buildID>/<buildTarget>/<locale>/<channel>/<osVersion>/<distribution>/<distVersion>/update.xml', view_func=ClientRequestView.as_view('clientrequest3'), defaults={'queryVersion': 3}, ) app.add_url_rule( '/update/4/<product>/<version>/<buildID>/<buildTarget>/<locale>/<channel>/<osVersion>/<distribution>/<distVersion>/<platformVersion>/update.xml', view_func=ClientRequestView.as_view('clientrequest4'), defaults={'queryVersion': 4}, )
class ClientTest(unittest.TestCase): maxDiff = 2000 @classmethod def setUpClass(cls): # Error handlers are removed in order to give us better debug messages cls.error_spec = app.error_handler_spec # Ripped from https://github.com/mitsuhiko/flask/blob/1f5927eee2288b4aaf508af5dc1f148aa2140d91/flask/app.py#L394 app.error_handler_spec = {None: {}} @classmethod def tearDownClass(cls): app.error_handler_spec = cls.error_spec def setUp(self): self.cef_fd, self.cef_file = mkstemp() app.config['DEBUG'] = True app.config['SPECIAL_FORCE_HOSTS'] = ('http://a.com', ) app.config['WHITELISTED_DOMAINS'] = ('a.com', 'boring.com') dbo.setDb('sqlite:///:memory:') dbo.create() dbo.setDomainWhitelist(('a.com', 'boring.com')) self.client = app.test_client() self.view = ClientRequestView() auslib.log.cef_config = auslib.log.get_cef_config(self.cef_file) dbo.rules.t.insert().execute(backgroundRate=100, mapping='b', update_type='minor', product='b', data_version=1) dbo.releases.t.insert().execute(name='b', product='b', version='1.0', data_version=1, data=""" { "name": "b", "schema_version": 1, "appv": "1.0", "extv": "1.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "2", "locales": { "l": { "complete": { "filesize": "3", "from": "*", "hashValue": "4", "fileUrl": "http://a.com/z" } }, "xh": { "complete": { "filesize": "5", "from": "*", "hashValue": "6", "fileUrl": "http://a.com/x" } } } } } } """) dbo.rules.t.insert().execute(backgroundRate=100, mapping='c', update_type='minor', product='c', distribution='default', data_version=1) dbo.releases.t.insert().execute(name='c', product='c', version='10.0', data_version=1, data=""" { "name": "c", "schema_version": 1, "appv": "10.0", "extv": "10.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "11", "locales": { "l": { "complete": { "filesize": "12", "from": "*", "hashValue": "13", "fileUrl": "http://a.com/y" } } } } } } """) dbo.rules.t.insert().execute(backgroundRate=100, mapping='d', update_type='minor', product='d', data_version=1) dbo.releases.t.insert().execute(name='d', product='d', version='20.0', data_version=1, data=""" { "name": "d", "schema_version": 1, "appv": "20.0", "extv": "20.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "21", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://evil.com/y" } } } } } } """) dbo.rules.t.insert().execute(backgroundRate=100, mapping='e', update_type='minor', product='e', data_version=1) dbo.releases.t.insert().execute(name='e', product='e', version='22.0', data_version=1, data=""" { "name": "e", "schema_version": 1, "hashFunction": "sha512", "platforms": { "p": { "buildID": "25", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://a.com/y" } } } } } } """) def tearDown(self): os.close(self.cef_fd) os.remove(self.cef_file) def testGetHeaderArchitectureWindows(self): self.assertEqual( self.view.getHeaderArchitecture('WINNT_x86-msvc', 'Firefox Intel Windows'), 'Intel') def testGetHeaderArchitectureMacIntel(self): self.assertEqual( self.view.getHeaderArchitecture('Darwin_x86-gcc3-u-ppc-i386', 'Firefox Intel Mac'), 'Intel') def testGetHeaderArchitectureMacPPC(self): self.assertEqual( self.view.getHeaderArchitecture('Darwin_ppc-gcc3-u-ppc-i386', 'Firefox PPC Mac'), 'PPC') def testDontUpdateToYourself(self): ret = self.client.get('/update/3/b/1.0/2/p/l/a/a/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') self.assertEqual( minidom.parseString(ret.data).getElementsByTagName('updates') [0].firstChild.nodeValue, '\n') def testDontUpdateBackwards(self): ret = self.client.get('/update/3/b/1.0/5/p/l/a/a/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') self.assertEqual( minidom.parseString(ret.data).getElementsByTagName('updates') [0].firstChild.nodeValue, '\n') def testDontDecreaseVersion(self): ret = self.client.get( '/update/3/c/15.0/1/p/l/a/a/default/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') self.assertEqual( minidom.parseString(ret.data).getElementsByTagName('updates') [0].firstChild.nodeValue, '\n') def testVersion1Get(self): ret = self.client.get("/update/1/b/1.0/1/p/l/a/update.xml") self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') # We need to load and re-xmlify these to make sure we don't get failures due to whitespace differences. returned = minidom.parseString(ret.data) expected = minidom.parseString("""<?xml version="1.0"?> <updates> <update type="minor" version="1.0" extensionVersion="1.0" buildID="2"> <patch type="complete" URL="http://a.com/z" hashFunction="sha512" hashValue="4" size="3"/> </update> </updates> """) self.assertEqual(returned.toxml(), expected.toxml()) def testVersion2Get(self): ret = self.client.get('/update/2/b/1.0/0/p/l/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') # We need to load and re-xmlify these to make sure we don't get failures due to whitespace differences. returned = minidom.parseString(ret.data) expected = minidom.parseString("""<?xml version="1.0"?> <updates> <update type="minor" version="1.0" extensionVersion="1.0" buildID="2"> <patch type="complete" URL="http://a.com/z" hashFunction="sha512" hashValue="4" size="3"/> </update> </updates> """) self.assertEqual(returned.toxml(), expected.toxml()) def testVersion2GetIgnoresRuleWithDistribution(self): ret = self.client.get('/update/2/c/10.0/1/p/l/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') # We need to load and re-xmlify these to make sure we don't get failures due to whitespace differences. self.assertEqual( minidom.parseString(ret.data).getElementsByTagName('updates') [0].firstChild.nodeValue, '\n') def testVersion3Get(self): ret = self.client.get('/update/3/a/1.0/1/a/a/a/a/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') # An empty update contains an <updates> tag with a newline, which is what we're expecting here self.assertEqual( minidom.parseString(ret.data).getElementsByTagName('updates') [0].firstChild.nodeValue, '\n') def testVersion3GetWithUpdate(self): ret = self.client.get('/update/3/b/1.0/1/p/l/a/a/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') # We need to load and re-xmlify these to make sure we don't get failures due to whitespace differences. returned = minidom.parseString(ret.data) expected = minidom.parseString("""<?xml version="1.0"?> <updates> <update type="minor" version="1.0" extensionVersion="1.0" buildID="2"> <patch type="complete" URL="http://a.com/z" hashFunction="sha512" hashValue="4" size="3"/> </update> </updates> """) self.assertEqual(returned.toxml(), expected.toxml()) def testVersion4Get(self): ret = self.client.get('/update/4/b/1.0/1/p/l/a/a/a/a/1/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') # We need to load and re-xmlify these to make sure we don't get failures due to whitespace differences. returned = minidom.parseString(ret.data) expected = minidom.parseString("""<?xml version="1.0"?> <updates> <update type="minor" version="1.0" extensionVersion="1.0" buildID="2"> <patch type="complete" URL="http://a.com/z" hashFunction="sha512" hashValue="4" size="3"/> </update> </updates> """) self.assertEqual(returned.toxml(), expected.toxml()) def testGetURLNotInWhitelist(self): ret = self.client.get('/update/3/d/20.0/1/p/l/a/a/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') self.assertEqual( minidom.parseString(ret.data).getElementsByTagName('updates') [0].firstChild.nodeValue, '\n') def testEmptySnippetMissingExtv(self): ret = self.client.get('/update/3/e/20.0/1/p/l/a/a/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') self.assertEqual( minidom.parseString(ret.data).getElementsByTagName('updates') [0].firstChild.nodeValue, '\n') def testRobotsExists(self): ret = self.client.get('/robots.txt') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/plain') self.assertTrue('User-agent' in ret.data) def testBadAvastURLsFromBug1125231(self): # Some versions of Avast have a bug in them that prepends "x86 " # to the locale. We need to make sure we handle this case correctly # so that these people can keep up to date. ret = self.client.get('/update/4/b/1.0/1/p/x86 l/a/a/a/a/1/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') # Compare the Avast-style URL to the non-messed up equivalent. They # should get the same update XML. ret2 = self.client.get('/update/4/b/1.0/1/p/l/a/a/a/a/1/update.xml') self.assertEqual(ret2.status_code, 200) self.assertEqual(ret2.mimetype, 'text/xml') self.assertEqual(ret.data, ret2.data) def testFixForBug1125231DoesntBreakXhLocale(self): ret = self.client.get('/update/4/b/1.0/1/p/xh/a/a/a/a/1/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') returned = minidom.parseString(ret.data) expected = minidom.parseString("""<?xml version="1.0"?> <updates> <update type="minor" version="1.0" extensionVersion="1.0" buildID="2"> <patch type="complete" URL="http://a.com/x" hashFunction="sha512" hashValue="6" size="5"/> </update> </updates> """) self.assertEqual(returned.toxml(), expected.toxml()) def testAvastURLsWithBadQueryArgs(self): ret = self.client.get( "/update/4/b/1.0/1/p/l/a/a/a/a/1/update.xml?force=1%3Favast=1") self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') ret2 = self.client.get( '/update/4/b/1.0/1/p/l/a/a/a/a/1/update.xml?force=1') self.assertEqual(ret2.status_code, 200) self.assertEqual(ret2.mimetype, 'text/xml') self.assertEqual(ret.data, ret2.data) def testAvastURLsWithUnescapedBadQueryArgs(self): ret = self.client.get( "/update/4/b/1.0/1/p/l/a/a/a/a/1/update.xml?force=1?avast=1") self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') ret2 = self.client.get( '/update/4/b/1.0/1/p/l/a/a/a/a/1/update.xml?force=1') self.assertEqual(ret2.status_code, 200) self.assertEqual(ret2.mimetype, 'text/xml') self.assertEqual(ret.data, ret2.data) def testAvastURLsWithGoodQueryArgs(self): ret = self.client.get( "/update/4/b/1.0/1/p/l/a/a/a/a/1/update.xml?force=1&avast=1") self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') returned = minidom.parseString(ret.data) expected = minidom.parseString("""<?xml version="1.0"?> <updates> <update type="minor" version="1.0" extensionVersion="1.0" buildID="2"> <patch type="complete" URL="http://a.com/z?force=1" hashFunction="sha512" hashValue="4" size="3"/> </update> </updates> """) self.assertEqual(returned.toxml(), expected.toxml()) def testDeprecatedEsrVersionStyleGetsUpdates(self): ret = self.client.get('/update/3/b/1.0esrpre/1/p/l/a/a/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') # We need to load and re-xmlify these to make sure we don't get failures due to whitespace differences. returned = minidom.parseString(ret.data) expected = minidom.parseString("""<?xml version="1.0"?> <updates> <update type="minor" version="1.0" extensionVersion="1.0" buildID="2"> <patch type="complete" URL="http://a.com/z" hashFunction="sha512" hashValue="4" size="3"/> </update> </updates> """) self.assertEqual(returned.toxml(), expected.toxml())
def setUp(self): self.version_fd, self.version_file = mkstemp() app.config['DEBUG'] = True app.config['SPECIAL_FORCE_HOSTS'] = ('http://a.com', ) app.config['WHITELISTED_DOMAINS'] = { 'a.com': ('b', 'c', 'e', 'b2g', 'response-a', 'response-b', 's', 'responseblob-a', 'responseblob-b', 'q', 'fallback') } app.config["VERSION_FILE"] = self.version_file with open(self.version_file, "w+") as f: f.write(""" { "source":"https://github.com/mozilla/balrog", "version":"1.0", "commit":"abcdef123456" } """) dbo.setDb('sqlite:///:memory:') dbo.create() dbo.setDomainWhitelist({'a.com': ('b', 'c', 'e', 'b2g')}) self.client = app.test_client() self.view = ClientRequestView() dbo.rules.t.insert().execute(priority=90, backgroundRate=100, mapping='b', update_type='minor', product='b', data_version=1) dbo.releases.t.insert().execute(name='b', product='b', data_version=1, data=createBlob(""" { "name": "b", "schema_version": 1, "appv": "1.0", "extv": "1.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "2", "locales": { "l": { "complete": { "filesize": "3", "from": "*", "hashValue": "4", "fileUrl": "http://a.com/z" } }, "xh": { "complete": { "filesize": "5", "from": "*", "hashValue": "6", "fileUrl": "http://a.com/x" } } } } } } """)) dbo.rules.t.insert().execute(priority=90, backgroundRate=100, mapping='s', update_type='minor', product='s', systemCapabilities="SSE", data_version=1) dbo.releases.t.insert().execute(name='s', product='s', data_version=1, data=createBlob(""" { "name": "s", "schema_version": 1, "appv": "1.0", "extv": "1.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "5", "locales": { "l": { "complete": { "filesize": "5", "from": "*", "hashValue": "5", "fileUrl": "http://a.com/s" } } } } } } """)) dbo.rules.t.insert().execute(priority=90, backgroundRate=0, mapping='q', update_type='minor', product='q', fallbackMapping='fallback', data_version=1) dbo.releases.t.insert().execute(name='q', product='q', data_version=1, data=createBlob(""" { "name": "q", "schema_version": 1, "appv": "1.0", "extv": "1.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "5", "locales": { "l": { "complete": { "filesize": "5", "from": "*", "hashValue": "5", "fileUrl": "http://a.com/q" } } } } } } """)) dbo.releases.t.insert().execute(name='fallback', product='q', data_version=1, data=createBlob(""" { "name": "fallback", "schema_version": 1, "appv": "1.0", "extv": "1.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "5", "locales": { "l": { "complete": { "filesize": "5", "from": "*", "hashValue": "5", "fileUrl": "http://a.com/fallback" } } } } } } """)) dbo.rules.t.insert().execute(priority=90, backgroundRate=100, mapping='c', update_type='minor', product='c', distribution='default', data_version=1) dbo.releases.t.insert().execute(name='c', product='c', data_version=1, data=createBlob(""" { "name": "c", "schema_version": 1, "appv": "10.0", "extv": "10.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "11", "locales": { "l": { "complete": { "filesize": "12", "from": "*", "hashValue": "13", "fileUrl": "http://a.com/y" } } } } } } """)) dbo.rules.t.insert().execute(priority=90, backgroundRate=100, mapping='d', update_type='minor', product='d', data_version=1) dbo.releases.t.insert().execute(name='d', product='d', data_version=1, data=createBlob(""" { "name": "d", "schema_version": 1, "appv": "20.0", "extv": "20.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "21", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://evil.com/y" } } } } } } """)) dbo.rules.t.insert().execute(priority=90, backgroundRate=0, mapping='e', update_type='minor', product='e', data_version=1) dbo.releases.t.insert().execute(name='e', product='e', data_version=1, data=createBlob(""" { "name": "e", "schema_version": 1, "hashFunction": "sha512", "platforms": { "p": { "buildID": "25", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://a.com/y" } } } } } } """)) dbo.rules.t.insert().execute(priority=100, backgroundRate=100, mapping='foxfood-whitelisted', update_type='minor', product='b2g', whitelist='b2g-whitelist', data_version=1) dbo.rules.t.insert().execute(priority=90, backgroundRate=100, mapping='foxfood-whitelisted', update_type='minor', product='b2g', channel="foxfood", whitelist='b2g-whitelist', data_version=1) dbo.rules.t.insert().execute(priority=80, backgroundRate=100, mapping='foxfood-fallback', update_type='minor', product='b2g', data_version=1) dbo.releases.t.insert().execute(name='b2g-whitelist', product='b2g', data_version=1, data=createBlob(""" { "name": "b2g-whitelist", "schema_version": 3000, "whitelist": [ { "imei": "000000000000000" }, { "imei": "000000000000001" }, { "imei": "000000000000002" } ] } """)) dbo.releases.t.insert().execute(name='foxfood-whitelisted', product='b2g', data_version=1, data=createBlob(""" { "name": "foxfood-whitelisted", "schema_version": 1, "extv": "2.5", "hashFunction": "sha512", "platforms": { "p": { "buildID": "25", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://a.com/secrets" } } } } } } """)) dbo.releases.t.insert().execute(name='foxfood-fallback', product='b2g', data_version=1, data=createBlob(""" { "name": "foxfood-fallback", "schema_version": 1, "extv": "2.5", "hashFunction": "sha512", "platforms": { "p": { "buildID": "25", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://a.com/public" } } } } } } """)) dbo.rules.t.insert().execute(priority=200, backgroundRate=100, mapping='gmp', update_type='minor', product='gmp', data_version=1) dbo.rules.t.insert().execute(priority=200, backgroundRate=100, mapping='gmp-with-one-response-product', update_type='minor', product='gmp-with-one-response-product', data_version=1) dbo.rules.t.insert().execute(priority=190, backgroundRate=100, mapping='response-a', update_type='minor', product='response-a', data_version=1) dbo.rules.t.insert().execute(priority=180, backgroundRate=100, mapping='response-b', update_type='minor', product='response-b', data_version=1) dbo.releases.t.insert().execute( name='gmp-with-one-response-product', product='gmp-with-one-response-product', data_version=1, data=createBlob(""" { "name": "superblob", "schema_version": 4000, "products": ["response-a"] } """)) dbo.releases.t.insert().execute(name='gmp', product='gmp', data_version=1, data=createBlob(""" { "name": "superblob", "schema_version": 4000, "products": ["response-a", "response-b"] } """)) dbo.releases.t.insert().execute(name='response-a', product='response-a', data_version=1, data=createBlob(""" { "name": "response-a", "schema_version": 1, "extv": "2.5", "hashFunction": "sha512", "platforms": { "p": { "buildID": "25", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://a.com/public" } } } }, "q": { "buildID": "25", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://a.com/public-q" } } } } } } """)) dbo.releases.t.insert().execute(name='response-b', product='response-b', data_version=1, data=createBlob(""" { "name": "response-b", "schema_version": 1, "extv": "2.5", "hashFunction": "sha512", "platforms": { "p": { "buildID": "25", "locales": { "l": { "complete": { "filesize": 27777777, "from": "*", "hashValue": "23", "fileUrl": "http://a.com/b" } } } } } } """)) dbo.rules.t.insert().execute(priority=180, backgroundRate=100, mapping='systemaddons-uninstall', update_type='minor', product='systemaddons-uninstall', data_version=1) dbo.releases.t.insert().execute(name='systemaddons-uninstall', product='systemaddons-uninstall', data_version=1, data=createBlob(""" { "name": "fake", "schema_version": 5000, "hashFunction": "SHA512", "uninstall": true } """)) dbo.rules.t.insert().execute(priority=180, backgroundRate=100, mapping='systemaddons', update_type='minor', product='systemaddons', data_version=1) dbo.releases.t.insert().execute(name='systemaddons', product='systemaddons', data_version=1, data=createBlob(""" { "name": "fake", "schema_version": 5000, "hashFunction": "SHA512", "uninstall": false, "addons": { "c": { "version": "1", "platforms": { "p": { "filesize": 2, "hashValue": "3", "fileUrl": "http://a.com/blah" } } } } } """)) dbo.rules.t.insert().execute( priority=1000, backgroundRate=0, mapping='product_that_should_not_be_updated-1.1', update_type='minor', product='product_that_should_not_be_updated', data_version=1) dbo.releases.t.insert().execute( name='product_that_should_not_be_updated-1.1', product='product_that_should_not_be_updated', data_version=1, data=createBlob(""" { "name": "product_that_should_not_be_updated-1.1", "schema_version": 1, "appv": "1.1", "extv": "1.1", "hashFunction": "sha512", "platforms": { "p": { "buildID": "2", "locales": { "l": { "complete": { "filesize": "3", "from": "*", "hashValue": "4", "fileUrl": "http://a.com/z" } } } } } } """)) dbo.rules.t.insert().execute( priority=90, backgroundRate=100, mapping='product_that_should_not_be_updated-2.0', update_type='minor', product='product_that_should_not_be_updated', data_version=1) dbo.releases.t.insert().execute( name='product_that_should_not_be_updated-2.0', product='product_that_should_not_be_updated', data_version=1, data=createBlob(""" { "name": "product_that_should_not_be_updated-2.0", "schema_version": 1, "appv": "2.0", "extv": "2.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "2", "locales": { "l": { "complete": { "filesize": "3", "from": "*", "hashValue": "4", "fileUrl": "http://a.com/z" } } } } } } """)) dbo.rules.t.insert().execute( priority=200, backgroundRate=100, mapping='superblobaddon-with-multiple-response-blob', update_type='minor', product='superblobaddon-with-multiple-response-blob', data_version=1) dbo.rules.t.insert().execute( priority=200, backgroundRate=100, mapping='superblobaddon-with-one-response-blob', update_type='minor', product='superblobaddon-with-one-response-blob', data_version=1) dbo.releases.t.insert().execute( name='superblobaddon-with-one-response-blob', product='superblobaddon-with-one-response-blob', data_version=1, data=createBlob(""" { "name": "superblobaddon", "schema_version": 4000, "revision": 123, "blobs": ["responseblob-a"] } """)) dbo.releases.t.insert().execute( name='superblobaddon-with-multiple-response-blob', product='superblobaddon-with-multiple-response-blob', data_version=1, data=createBlob(""" { "name": "superblobaddon", "schema_version": 4000, "revision": 124, "blobs": ["responseblob-a", "responseblob-b"] } """)) dbo.releases.t.insert().execute(name='responseblob-a', product='responseblob-a', data_version=1, data=createBlob(""" { "name": "responseblob-a", "schema_version": 5000, "hashFunction": "SHA512", "addons": { "c": { "version": "1", "platforms": { "p": { "filesize": 2, "hashValue": "3", "fileUrl": "http://a.com/e" }, "q": { "filesize": 4, "hashValue": "5", "fileUrl": "http://a.com/e" }, "q2": { "alias": "q" } } }, "d": { "version": "5", "platforms": { "q": { "filesize": 10, "hashValue": "11", "fileUrl": "http://a.com/c" }, "default": { "filesize": 20, "hashValue": "50", "fileUrl": "http://a.com/c" } } } } } """)) dbo.releases.t.insert().execute(name='responseblob-b', product='responseblob-b', data_version=1, data=createBlob(""" { "name": "responseblob-b", "schema_version": 5000, "hashFunction": "sha512", "uninstall": false, "addons": { "b": { "version": "1", "platforms": { "p": { "filesize": 27, "hashValue": "23", "fileUrl": "http://a.com/b" } } } } } """))
a 200 response with no updates is returned, because that's what the client expects. See bugs 885173 and 1069454 for additional background.""" log.debug('Hit exception, sending an empty response', exc_info=True) response = make_response('<?xml version="1.0"?>\n<updates>\n</updates>') response.mimetype = 'text/xml' return response @app.route('/robots.txt') def robots(): return send_from_directory(app.static_folder, "robots.txt") # The "main" routes. 99% of requests will come in through these. app.add_url_rule( "/update/1/<product>/<version>/<buildID>/<buildTarget>/<locale>/<channel>/update.xml", view_func=ClientRequestView.as_view("clientrequest1"), # Underlying code depends on osVersion being set. Since this route only # exists to support ancient queries, and all newer versions have osVersion # in them it's easier to set this here than make the all of the underlying # code support queries without it. defaults={"queryVersion": 2, "osVersion": ""}, ) app.add_url_rule( '/update/2/<product>/<version>/<buildID>/<buildTarget>/<locale>/<channel>/<osVersion>/update.xml', view_func=ClientRequestView.as_view('clientrequest2'), defaults={'queryVersion': 2}, ) app.add_url_rule( '/update/3/<product>/<version>/<buildID>/<buildTarget>/<locale>/<channel>/<osVersion>/<distribution>/<distVersion>/update.xml', view_func=ClientRequestView.as_view('clientrequest3'), defaults={'queryVersion': 3},
def setUp(self): self.cef_fd, self.cef_file = mkstemp() app.config['DEBUG'] = True app.config['SPECIAL_FORCE_HOSTS'] = ('http://a.com',) app.config['WHITELISTED_DOMAINS'] = ('a.com', 'boring.com') dbo.setDb('sqlite:///:memory:') dbo.create() dbo.setDomainWhitelist(('a.com', 'boring.com')) self.client = app.test_client() self.view = ClientRequestView() auslib.log.cef_config = auslib.log.get_cef_config(self.cef_file) dbo.rules.t.insert().execute(backgroundRate=100, mapping='b', update_type='minor', product='b', data_version=1) dbo.releases.t.insert().execute(name='b', product='b', version='1.0', data_version=1, data=""" { "name": "b", "schema_version": 1, "appv": "1.0", "extv": "1.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "2", "locales": { "l": { "complete": { "filesize": "3", "from": "*", "hashValue": "4", "fileUrl": "http://a.com/z" } }, "xh": { "complete": { "filesize": "5", "from": "*", "hashValue": "6", "fileUrl": "http://a.com/x" } } } } } } """) dbo.rules.t.insert().execute(backgroundRate=100, mapping='c', update_type='minor', product='c', distribution='default', data_version=1) dbo.releases.t.insert().execute(name='c', product='c', version='10.0', data_version=1, data=""" { "name": "c", "schema_version": 1, "appv": "10.0", "extv": "10.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "11", "locales": { "l": { "complete": { "filesize": "12", "from": "*", "hashValue": "13", "fileUrl": "http://a.com/y" } } } } } } """) dbo.rules.t.insert().execute(backgroundRate=100, mapping='d', update_type='minor', product='d', data_version=1) dbo.releases.t.insert().execute(name='d', product='d', version='20.0', data_version=1, data=""" { "name": "d", "schema_version": 1, "appv": "20.0", "extv": "20.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "21", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://evil.com/y" } } } } } } """) dbo.rules.t.insert().execute(backgroundRate=100, mapping='e', update_type='minor', product='e', data_version=1) dbo.releases.t.insert().execute(name='e', product='e', version='22.0', data_version=1, data=""" { "name": "e", "schema_version": 1, "hashFunction": "sha512", "platforms": { "p": { "buildID": "25", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://a.com/y" } } } } } } """) dbo.rules.t.insert().execute(backgroundRate=100, mapping='foxfood', update_type='minor', product='b2g', whitelist='b2g-whitelist', data_version=1) dbo.releases.t.insert().execute(name='b2g-whitelist', product='b2g', version='2.5', data_version=1, data=""" { "name": "b2g-whitelist", "schema_version": 3000, "whitelist": [ { "imei": "000000000000000" }, { "imei": "000000000000001" }, { "imei": "000000000000002" } ] } """) dbo.releases.t.insert().execute(name='foxfood', product='b2g', version='2.5', data_version=1, data=""" { "name": "foxfood", "schema_version": 1, "extv": "2.5", "hashFunction": "sha512", "platforms": { "p": { "buildID": "25", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://a.com/y" } } } } } } """)
def fourohfour(error): """We don't return 404s in AUS. Instead, we return empty XML files""" response = make_response('<?xml version="1.0"?>\n<updates>\n</updates>') response.mimetype = 'text/xml' return response @app.errorhandler(500) def isa(error): log.error("Caught ISE 500 error.") log.debug("Balrog version is: %s", version) log.debug("Request path is: %s", request.path) log.debug("Request environment is: %s", request.environ) log.debug("Request headers are: %s", request.headers) return error app.add_url_rule( '/update/2/<product>/<version>/<buildID>/<buildTarget>/<locale>/<channel>/<osVersion>/update.xml', view_func=ClientRequestView.as_view('clientrequest'), defaults={'queryVersion': 2}, ) app.add_url_rule( '/update/3/<product>/<version>/<buildID>/<buildTarget>/<locale>/<channel>/<osVersion>/<distribution>/<distVersion>/update.xml', view_func=ClientRequestView.as_view('clientrequest'), defaults={'queryVersion': 3}, ) app.add_url_rule( '/update/4/<product>/<version>/<buildID>/<buildTarget>/<locale>/<channel>/<osVersion>/<distribution>/<distVersion>/<platformVersion>/update.xml', view_func=ClientRequestView.as_view('clientrequest'), defaults={'queryVersion': 4}, )
class ClientTest(unittest.TestCase): maxDiff = 2000 @classmethod def setUpClass(cls): # Error handlers are removed in order to give us better debug messages cls.error_spec = app.error_handler_spec # Ripped from https://github.com/mitsuhiko/flask/blob/1f5927eee2288b4aaf508af5dc1f148aa2140d91/flask/app.py#L394 app.error_handler_spec = {None: {}} @classmethod def tearDownClass(cls): app.error_handler_spec = cls.error_spec def setUp(self): self.cef_fd, self.cef_file = mkstemp() app.config['DEBUG'] = True app.config['SPECIAL_FORCE_HOSTS'] = ('http://a.com',) app.config['WHITELISTED_DOMAINS'] = ('a.com', 'boring.com') dbo.setDb('sqlite:///:memory:') dbo.create() dbo.setDomainWhitelist(('a.com', 'boring.com')) self.client = app.test_client() self.view = ClientRequestView() auslib.log.cef_config = auslib.log.get_cef_config(self.cef_file) dbo.rules.t.insert().execute(backgroundRate=100, mapping='b', update_type='minor', product='b', data_version=1) dbo.releases.t.insert().execute(name='b', product='b', version='1.0', data_version=1, data=""" { "name": "b", "schema_version": 1, "appv": "1.0", "extv": "1.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "2", "locales": { "l": { "complete": { "filesize": "3", "from": "*", "hashValue": "4", "fileUrl": "http://a.com/z" } }, "xh": { "complete": { "filesize": "5", "from": "*", "hashValue": "6", "fileUrl": "http://a.com/x" } } } } } } """) dbo.rules.t.insert().execute(backgroundRate=100, mapping='c', update_type='minor', product='c', distribution='default', data_version=1) dbo.releases.t.insert().execute(name='c', product='c', version='10.0', data_version=1, data=""" { "name": "c", "schema_version": 1, "appv": "10.0", "extv": "10.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "11", "locales": { "l": { "complete": { "filesize": "12", "from": "*", "hashValue": "13", "fileUrl": "http://a.com/y" } } } } } } """) dbo.rules.t.insert().execute(backgroundRate=100, mapping='d', update_type='minor', product='d', data_version=1) dbo.releases.t.insert().execute(name='d', product='d', version='20.0', data_version=1, data=""" { "name": "d", "schema_version": 1, "appv": "20.0", "extv": "20.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "21", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://evil.com/y" } } } } } } """) dbo.rules.t.insert().execute(backgroundRate=100, mapping='e', update_type='minor', product='e', data_version=1) dbo.releases.t.insert().execute(name='e', product='e', version='22.0', data_version=1, data=""" { "name": "e", "schema_version": 1, "hashFunction": "sha512", "platforms": { "p": { "buildID": "25", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://a.com/y" } } } } } } """) dbo.rules.t.insert().execute(backgroundRate=100, mapping='foxfood', update_type='minor', product='b2g', whitelist='b2g-whitelist', data_version=1) dbo.releases.t.insert().execute(name='b2g-whitelist', product='b2g', version='2.5', data_version=1, data=""" { "name": "b2g-whitelist", "schema_version": 3000, "whitelist": [ { "imei": "000000000000000" }, { "imei": "000000000000001" }, { "imei": "000000000000002" } ] } """) dbo.releases.t.insert().execute(name='foxfood', product='b2g', version='2.5', data_version=1, data=""" { "name": "foxfood", "schema_version": 1, "extv": "2.5", "hashFunction": "sha512", "platforms": { "p": { "buildID": "25", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://a.com/y" } } } } } } """) def tearDown(self): os.close(self.cef_fd) os.remove(self.cef_file) def testGetHeaderArchitectureWindows(self): self.assertEqual(self.view.getHeaderArchitecture('WINNT_x86-msvc', 'Firefox Intel Windows'), 'Intel') def testGetHeaderArchitectureMacIntel(self): self.assertEqual(self.view.getHeaderArchitecture('Darwin_x86-gcc3-u-ppc-i386', 'Firefox Intel Mac'), 'Intel') def testGetHeaderArchitectureMacPPC(self): self.assertEqual(self.view.getHeaderArchitecture('Darwin_ppc-gcc3-u-ppc-i386', 'Firefox PPC Mac'), 'PPC') def testDontUpdateToYourself(self): ret = self.client.get('/update/3/b/1.0/2/p/l/a/a/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') self.assertEqual(minidom.parseString(ret.data).getElementsByTagName('updates')[0].firstChild.nodeValue, '\n') def testDontUpdateBackwards(self): ret = self.client.get('/update/3/b/1.0/5/p/l/a/a/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') self.assertEqual(minidom.parseString(ret.data).getElementsByTagName('updates')[0].firstChild.nodeValue, '\n') def testDontDecreaseVersion(self): ret = self.client.get('/update/3/c/15.0/1/p/l/a/a/default/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') self.assertEqual(minidom.parseString(ret.data).getElementsByTagName('updates')[0].firstChild.nodeValue, '\n') def testVersion1Get(self): ret = self.client.get("/update/1/b/1.0/1/p/l/a/update.xml") self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') # We need to load and re-xmlify these to make sure we don't get failures due to whitespace differences. returned = minidom.parseString(ret.data) expected = minidom.parseString("""<?xml version="1.0"?> <updates> <update type="minor" version="1.0" extensionVersion="1.0" buildID="2"> <patch type="complete" URL="http://a.com/z" hashFunction="sha512" hashValue="4" size="3"/> </update> </updates> """) self.assertEqual(returned.toxml(), expected.toxml()) def testVersion2Get(self): ret = self.client.get('/update/2/b/1.0/0/p/l/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') # We need to load and re-xmlify these to make sure we don't get failures due to whitespace differences. returned = minidom.parseString(ret.data) expected = minidom.parseString("""<?xml version="1.0"?> <updates> <update type="minor" version="1.0" extensionVersion="1.0" buildID="2"> <patch type="complete" URL="http://a.com/z" hashFunction="sha512" hashValue="4" size="3"/> </update> </updates> """) self.assertEqual(returned.toxml(), expected.toxml()) def testVersion2GetIgnoresRuleWithDistribution(self): ret = self.client.get('/update/2/c/10.0/1/p/l/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') # We need to load and re-xmlify these to make sure we don't get failures due to whitespace differences. self.assertEqual(minidom.parseString(ret.data).getElementsByTagName('updates')[0].firstChild.nodeValue, '\n') def testVersion3Get(self): ret = self.client.get('/update/3/a/1.0/1/a/a/a/a/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') # An empty update contains an <updates> tag with a newline, which is what we're expecting here self.assertEqual(minidom.parseString(ret.data).getElementsByTagName('updates')[0].firstChild.nodeValue, '\n') def testVersion3GetWithUpdate(self): ret = self.client.get('/update/3/b/1.0/1/p/l/a/a/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') # We need to load and re-xmlify these to make sure we don't get failures due to whitespace differences. returned = minidom.parseString(ret.data) expected = minidom.parseString("""<?xml version="1.0"?> <updates> <update type="minor" version="1.0" extensionVersion="1.0" buildID="2"> <patch type="complete" URL="http://a.com/z" hashFunction="sha512" hashValue="4" size="3"/> </update> </updates> """) self.assertEqual(returned.toxml(), expected.toxml()) def testVersion4Get(self): ret = self.client.get('/update/4/b/1.0/1/p/l/a/a/a/a/1/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') # We need to load and re-xmlify these to make sure we don't get failures due to whitespace differences. returned = minidom.parseString(ret.data) expected = minidom.parseString("""<?xml version="1.0"?> <updates> <update type="minor" version="1.0" extensionVersion="1.0" buildID="2"> <patch type="complete" URL="http://a.com/z" hashFunction="sha512" hashValue="4" size="3"/> </update> </updates> """) self.assertEqual(returned.toxml(), expected.toxml()) def testVersion5GetWhitelisted(self): ret = self.client.get('/update/5/b2g/1.0/1/p/l/a/a/a/a/000000000000001/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') # We need to load and re-xmlify these to make sure we don't get failures due to whitespace differences. returned = minidom.parseString(ret.data) expected = minidom.parseString("""<?xml version="1.0"?> <updates> <update type="minor" version="None" extensionVersion="2.5" buildID="25"> <patch type="complete" URL="http://a.com/y" hashFunction="sha512" hashValue="23" size="22"/> </update> </updates> """) self.assertEqual(returned.toxml(), expected.toxml()) def testVersion5GetNotWhitelisted(self): ret = self.client.get('/update/5/b2g/1.0/1/p/l/a/a/a/a/000000000000009/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') # We need to load and re-xmlify these to make sure we don't get failures due to whitespace differences. returned = minidom.parseString(ret.data) expected = minidom.parseString("""<?xml version="1.0"?> <updates> </updates> """) self.assertEqual(returned.toxml(), expected.toxml()) def testGetURLNotInWhitelist(self): ret = self.client.get('/update/3/d/20.0/1/p/l/a/a/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') self.assertEqual(minidom.parseString(ret.data).getElementsByTagName('updates')[0].firstChild.nodeValue, '\n') def testEmptySnippetMissingExtv(self): ret = self.client.get('/update/3/e/20.0/1/p/l/a/a/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') self.assertEqual(minidom.parseString(ret.data).getElementsByTagName('updates')[0].firstChild.nodeValue, '\n') def testRobotsExists(self): ret = self.client.get('/robots.txt') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/plain') self.assertTrue('User-agent' in ret.data) def testBadAvastURLsFromBug1125231(self): # Some versions of Avast have a bug in them that prepends "x86 " # to the locale. We need to make sure we handle this case correctly # so that these people can keep up to date. ret = self.client.get('/update/4/b/1.0/1/p/x86 l/a/a/a/a/1/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') # Compare the Avast-style URL to the non-messed up equivalent. They # should get the same update XML. ret2 = self.client.get('/update/4/b/1.0/1/p/l/a/a/a/a/1/update.xml') self.assertEqual(ret2.status_code, 200) self.assertEqual(ret2.mimetype, 'text/xml') self.assertEqual(ret.data, ret2.data) def testFixForBug1125231DoesntBreakXhLocale(self): ret = self.client.get('/update/4/b/1.0/1/p/xh/a/a/a/a/1/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') returned = minidom.parseString(ret.data) expected = minidom.parseString("""<?xml version="1.0"?> <updates> <update type="minor" version="1.0" extensionVersion="1.0" buildID="2"> <patch type="complete" URL="http://a.com/x" hashFunction="sha512" hashValue="6" size="5"/> </update> </updates> """) self.assertEqual(returned.toxml(), expected.toxml()) def testAvastURLsWithBadQueryArgs(self): ret = self.client.get("/update/4/b/1.0/1/p/l/a/a/a/a/1/update.xml?force=1%3Favast=1") self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') ret2 = self.client.get('/update/4/b/1.0/1/p/l/a/a/a/a/1/update.xml?force=1') self.assertEqual(ret2.status_code, 200) self.assertEqual(ret2.mimetype, 'text/xml') self.assertEqual(ret.data, ret2.data) def testAvastURLsWithUnescapedBadQueryArgs(self): ret = self.client.get("/update/4/b/1.0/1/p/l/a/a/a/a/1/update.xml?force=1?avast=1") self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') ret2 = self.client.get('/update/4/b/1.0/1/p/l/a/a/a/a/1/update.xml?force=1') self.assertEqual(ret2.status_code, 200) self.assertEqual(ret2.mimetype, 'text/xml') self.assertEqual(ret.data, ret2.data) def testAvastURLsWithGoodQueryArgs(self): ret = self.client.get("/update/4/b/1.0/1/p/l/a/a/a/a/1/update.xml?force=1&avast=1") self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') returned = minidom.parseString(ret.data) expected = minidom.parseString("""<?xml version="1.0"?> <updates> <update type="minor" version="1.0" extensionVersion="1.0" buildID="2"> <patch type="complete" URL="http://a.com/z?force=1" hashFunction="sha512" hashValue="4" size="3"/> </update> </updates> """) self.assertEqual(returned.toxml(), expected.toxml()) def testDeprecatedEsrVersionStyleGetsUpdates(self): ret = self.client.get('/update/3/b/1.0esrpre/1/p/l/a/a/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') # We need to load and re-xmlify these to make sure we don't get failures due to whitespace differences. returned = minidom.parseString(ret.data) expected = minidom.parseString("""<?xml version="1.0"?> <updates> <update type="minor" version="1.0" extensionVersion="1.0" buildID="2"> <patch type="complete" URL="http://a.com/z" hashFunction="sha512" hashValue="4" size="3"/> </update> </updates> """) self.assertEqual(returned.toxml(), expected.toxml())
class ClientTest(unittest.TestCase): maxDiff = 2000 @classmethod def setUpClass(cls): # Error handlers are removed in order to give us better debug messages cls.error_spec = app.error_handler_spec # Ripped from https://github.com/mitsuhiko/flask/blob/1f5927eee2288b4aaf508af5dc1f148aa2140d91/flask/app.py#L394 app.error_handler_spec = {None: {}} @classmethod def tearDownClass(cls): app.error_handler_spec = cls.error_spec def setUp(self): self.cef_fd, self.cef_file = mkstemp() app.config['DEBUG'] = True app.config['SPECIAL_FORCE_HOSTS'] = ('http://a.com',) app.config['WHITELISTED_DOMAINS'] = ('a.com', 'boring.com') dbo.setDb('sqlite:///:memory:') dbo.create() dbo.setDomainWhitelist(('a.com', 'boring.com')) self.client = app.test_client() self.view = ClientRequestView() auslib.log.cef_config = auslib.log.get_cef_config(self.cef_file) dbo.rules.t.insert().execute(backgroundRate=100, mapping='b', update_type='minor', product='b', data_version=1) dbo.releases.t.insert().execute(name='b', product='b', version='1.0', data_version=1, data=""" { "name": "b", "schema_version": 1, "appv": "1.0", "extv": "1.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "2", "locales": { "l": { "complete": { "filesize": "3", "from": "*", "hashValue": "4", "fileUrl": "http://a.com/z" } } } } } } """) dbo.rules.t.insert().execute(backgroundRate=100, mapping='c', update_type='minor', product='c', distribution='default', data_version=1) dbo.releases.t.insert().execute(name='c', product='c', version='10.0', data_version=1, data=""" { "name": "c", "schema_version": 1, "appv": "10.0", "extv": "10.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "11", "locales": { "l": { "complete": { "filesize": "12", "from": "*", "hashValue": "13", "fileUrl": "http://a.com/y" } } } } } } """) dbo.rules.t.insert().execute(backgroundRate=100, mapping='d', update_type='minor', product='d', data_version=1) dbo.releases.t.insert().execute(name='d', product='d', version='20.0', data_version=1, data=""" { "name": "d", "schema_version": 1, "appv": "20.0", "extv": "20.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "21", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://evil.com/y" } } } } } } """) dbo.rules.t.insert().execute(backgroundRate=100, mapping='e', update_type='minor', product='e', data_version=1) dbo.releases.t.insert().execute(name='e', product='e', version='22.0', data_version=1, data=""" { "name": "e", "schema_version": 1, "hashFunction": "sha512", "platforms": { "p": { "buildID": "25", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://a.com/y" } } } } } } """) def tearDown(self): os.close(self.cef_fd) os.remove(self.cef_file) def testGetHeaderArchitectureWindows(self): self.assertEqual(self.view.getHeaderArchitecture('WINNT_x86-msvc', 'Firefox Intel Windows'), 'Intel') def testGetHeaderArchitectureMacIntel(self): self.assertEqual(self.view.getHeaderArchitecture('Darwin_x86-gcc3-u-ppc-i386', 'Firefox Intel Mac'), 'Intel') def testGetHeaderArchitectureMacPPC(self): self.assertEqual(self.view.getHeaderArchitecture('Darwin_ppc-gcc3-u-ppc-i386', 'Firefox PPC Mac'), 'PPC') def testDontUpdateToYourself(self): ret = self.client.get('/update/3/b/1.0/2/p/l/a/a/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') self.assertEqual(minidom.parseString(ret.data).getElementsByTagName('updates')[0].firstChild.nodeValue, '\n') def testDontUpdateBackwards(self): ret = self.client.get('/update/3/b/1.0/5/p/l/a/a/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') self.assertEqual(minidom.parseString(ret.data).getElementsByTagName('updates')[0].firstChild.nodeValue, '\n') def testDontDecreaseVersion(self): ret = self.client.get('/update/3/c/15.0/1/p/l/a/a/default/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') self.assertEqual(minidom.parseString(ret.data).getElementsByTagName('updates')[0].firstChild.nodeValue, '\n') def testVersion3Get(self): ret = self.client.get('/update/3/a/1.0/1/a/a/a/a/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') # An empty update contains an <updates> tag with a newline, which is what we're expecting here self.assertEqual(minidom.parseString(ret.data).getElementsByTagName('updates')[0].firstChild.nodeValue, '\n') def testVersion3GetWithUpdate(self): ret = self.client.get('/update/3/b/1.0/1/p/l/a/a/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') # We need to load and re-xmlify these to make sure we don't get failures due to whitespace differences. returned = minidom.parseString(ret.data) expected = minidom.parseString("""<?xml version="1.0"?> <updates> <update type="minor" version="1.0" extensionVersion="1.0" buildID="2"> <patch type="complete" URL="http://a.com/z" hashFunction="sha512" hashValue="4" size="3"/> </update> </updates> """) self.assertEqual(returned.toxml(), expected.toxml()) def testVersion2Get(self): ret = self.client.get('/update/2/b/1.0/0/p/l/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') # We need to load and re-xmlify these to make sure we don't get failures due to whitespace differences. returned = minidom.parseString(ret.data) expected = minidom.parseString("""<?xml version="1.0"?> <updates> <update type="minor" version="1.0" extensionVersion="1.0" buildID="2"> <patch type="complete" URL="http://a.com/z" hashFunction="sha512" hashValue="4" size="3"/> </update> </updates> """) self.assertEqual(returned.toxml(), expected.toxml()) def testVersion2GetIgnoresRuleWithDistribution(self): ret = self.client.get('/update/2/c/10.0/1/p/l/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') # We need to load and re-xmlify these to make sure we don't get failures due to whitespace differences. self.assertEqual(minidom.parseString(ret.data).getElementsByTagName('updates')[0].firstChild.nodeValue, '\n') def testVersion4Get(self): ret = self.client.get('/update/4/b/1.0/1/p/l/a/a/a/a/1/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') # We need to load and re-xmlify these to make sure we don't get failures due to whitespace differences. returned = minidom.parseString(ret.data) expected = minidom.parseString("""<?xml version="1.0"?> <updates> <update type="minor" version="1.0" extensionVersion="1.0" buildID="2"> <patch type="complete" URL="http://a.com/z" hashFunction="sha512" hashValue="4" size="3"/> </update> </updates> """) self.assertEqual(returned.toxml(), expected.toxml()) def testGetURLNotInWhitelist(self): ret = self.client.get('/update/3/d/20.0/1/p/l/a/a/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') self.assertEqual(minidom.parseString(ret.data).getElementsByTagName('updates')[0].firstChild.nodeValue, '\n') def testEmptySnippetMissingExtv(self): ret = self.client.get('/update/3/e/20.0/1/p/l/a/a/a/a/update.xml') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/xml') self.assertEqual(minidom.parseString(ret.data).getElementsByTagName('updates')[0].firstChild.nodeValue, '\n') def testRobotsExists(self): ret = self.client.get('/robots.txt') self.assertEqual(ret.status_code, 200) self.assertEqual(ret.mimetype, 'text/plain') self.assertTrue('User-agent' in ret.data)
def setUp(self): self.cef_fd, self.cef_file = mkstemp() self.version_fd, self.version_file = mkstemp() app.config['DEBUG'] = True app.config['SPECIAL_FORCE_HOSTS'] = ('http://a.com', ) app.config['WHITELISTED_DOMAINS'] = ('a.com', 'boring.com') app.config["VERSION_FILE"] = self.version_file with open(self.version_file, "w+") as f: f.write(""" { "source":"https://github.com/mozilla/balrog", "version":"1.0", "commit":"abcdef123456" } """) dbo.setDb('sqlite:///:memory:') dbo.create() dbo.setDomainWhitelist(('a.com', 'boring.com')) self.client = app.test_client() self.view = ClientRequestView() auslib.log.cef_config = auslib.log.get_cef_config(self.cef_file) dbo.rules.t.insert().execute(backgroundRate=100, mapping='b', update_type='minor', product='b', data_version=1) dbo.releases.t.insert().execute(name='b', product='b', data_version=1, data=""" { "name": "b", "schema_version": 1, "appv": "1.0", "extv": "1.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "2", "locales": { "l": { "complete": { "filesize": "3", "from": "*", "hashValue": "4", "fileUrl": "http://a.com/z" } }, "xh": { "complete": { "filesize": "5", "from": "*", "hashValue": "6", "fileUrl": "http://a.com/x" } } } } } } """) dbo.rules.t.insert().execute(backgroundRate=100, mapping='c', update_type='minor', product='c', distribution='default', data_version=1) dbo.releases.t.insert().execute(name='c', product='c', data_version=1, data=""" { "name": "c", "schema_version": 1, "appv": "10.0", "extv": "10.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "11", "locales": { "l": { "complete": { "filesize": "12", "from": "*", "hashValue": "13", "fileUrl": "http://a.com/y" } } } } } } """) dbo.rules.t.insert().execute(backgroundRate=100, mapping='d', update_type='minor', product='d', data_version=1) dbo.releases.t.insert().execute(name='d', product='d', data_version=1, data=""" { "name": "d", "schema_version": 1, "appv": "20.0", "extv": "20.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "21", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://evil.com/y" } } } } } } """) dbo.rules.t.insert().execute(backgroundRate=100, mapping='e', update_type='minor', product='e', data_version=1) dbo.releases.t.insert().execute(name='e', product='e', data_version=1, data=""" { "name": "e", "schema_version": 1, "hashFunction": "sha512", "platforms": { "p": { "buildID": "25", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://a.com/y" } } } } } } """) dbo.rules.t.insert().execute(priority=100, backgroundRate=100, mapping='foxfood-whitelisted', update_type='minor', product='b2g', whitelist='b2g-whitelist', data_version=1) dbo.rules.t.insert().execute(priority=90, backgroundRate=100, mapping='foxfood-whitelisted', update_type='minor', product='b2g', channel="foxfood", whitelist='b2g-whitelist', data_version=1) dbo.rules.t.insert().execute(priority=80, backgroundRate=100, mapping='foxfood-fallback', update_type='minor', product='b2g', data_version=1) dbo.releases.t.insert().execute(name='b2g-whitelist', product='b2g', data_version=1, data=""" { "name": "b2g-whitelist", "schema_version": 3000, "whitelist": [ { "imei": "000000000000000" }, { "imei": "000000000000001" }, { "imei": "000000000000002" } ] } """) dbo.releases.t.insert().execute(name='foxfood-whitelisted', product='b2g', data_version=1, data=""" { "name": "foxfood-whitelisted", "schema_version": 1, "extv": "2.5", "hashFunction": "sha512", "platforms": { "p": { "buildID": "25", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://a.com/secrets" } } } } } } """) dbo.releases.t.insert().execute(name='foxfood-fallback', product='b2g', data_version=1, data=""" { "name": "foxfood-fallback", "schema_version": 1, "extv": "2.5", "hashFunction": "sha512", "platforms": { "p": { "buildID": "25", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://a.com/public" } } } } } } """)
) @app.route( "/update/5/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/%IMEI%/update.xml" ) @app.route( "/update/6/%PRODUCT%/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%SYSTEM_CAPABILITIES%/%DISTRIBUTION%" "/%DISTRIBUTION_VERSION%/update.xml" ) def unsubstituted_url_variables(): abort(404) # The "main" routes. 99% of requests will come in through these. app.add_url_rule( "/update/1/<product>/<version>/<buildID>/<buildTarget>/<locale>/<channel>/update.xml", view_func=ClientRequestView.as_view("clientrequest1"), # Underlying code depends on osVersion being set. Since this route only # exists to support ancient queries, and all newer versions have osVersion # in them it's easier to set this here than make the all of the underlying # code support queries without it. defaults={"queryVersion": 2, "osVersion": ""}, ) app.add_url_rule( "/update/2/<product>/<version>/<buildID>/<buildTarget>/<locale>/<channel>/<osVersion>/update.xml", view_func=ClientRequestView.as_view("clientrequest2"), defaults={"queryVersion": 2}, ) app.add_url_rule( "/update/3/<product>/<version>/<buildID>/<buildTarget>/<locale>/<channel>/<osVersion>/<distribution>/<distVersion>/update.xml", view_func=ClientRequestView.as_view("clientrequest3"), defaults={"queryVersion": 3},
def setUp(self): self.cef_fd, self.cef_file = mkstemp() app.config["DEBUG"] = True app.config["SPECIAL_FORCE_HOSTS"] = ("http://a.com",) app.config["WHITELISTED_DOMAINS"] = ("a.com", "boring.com") dbo.setDb("sqlite:///:memory:") dbo.create() dbo.setDomainWhitelist(("a.com", "boring.com")) self.client = app.test_client() self.view = ClientRequestView() auslib.log.cef_config = auslib.log.get_cef_config(self.cef_file) dbo.rules.t.insert().execute(backgroundRate=100, mapping="b", update_type="minor", product="b", data_version=1) dbo.releases.t.insert().execute( name="b", product="b", version="1.0", data_version=1, data=""" { "name": "b", "schema_version": 1, "appv": "1.0", "extv": "1.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "2", "locales": { "l": { "complete": { "filesize": "3", "from": "*", "hashValue": "4", "fileUrl": "http://a.com/z" } }, "xh": { "complete": { "filesize": "5", "from": "*", "hashValue": "6", "fileUrl": "http://a.com/x" } } } } } } """, ) dbo.rules.t.insert().execute( backgroundRate=100, mapping="c", update_type="minor", product="c", distribution="default", data_version=1 ) dbo.releases.t.insert().execute( name="c", product="c", version="10.0", data_version=1, data=""" { "name": "c", "schema_version": 1, "appv": "10.0", "extv": "10.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "11", "locales": { "l": { "complete": { "filesize": "12", "from": "*", "hashValue": "13", "fileUrl": "http://a.com/y" } } } } } } """, ) dbo.rules.t.insert().execute(backgroundRate=100, mapping="d", update_type="minor", product="d", data_version=1) dbo.releases.t.insert().execute( name="d", product="d", version="20.0", data_version=1, data=""" { "name": "d", "schema_version": 1, "appv": "20.0", "extv": "20.0", "hashFunction": "sha512", "platforms": { "p": { "buildID": "21", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://evil.com/y" } } } } } } """, ) dbo.rules.t.insert().execute(backgroundRate=100, mapping="e", update_type="minor", product="e", data_version=1) dbo.releases.t.insert().execute( name="e", product="e", version="22.0", data_version=1, data=""" { "name": "e", "schema_version": 1, "hashFunction": "sha512", "platforms": { "p": { "buildID": "25", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://a.com/y" } } } } } } """, ) dbo.rules.t.insert().execute( priority=100, backgroundRate=100, mapping="foxfood-whitelisted", update_type="minor", product="b2g", whitelist="b2g-whitelist", data_version=1, ) dbo.rules.t.insert().execute( priority=90, backgroundRate=100, mapping="foxfood-whitelisted", update_type="minor", product="b2g", channel="foxfood", whitelist="b2g-whitelist", data_version=1, ) dbo.rules.t.insert().execute( priority=80, backgroundRate=100, mapping="foxfood-fallback", update_type="minor", product="b2g", data_version=1, ) dbo.releases.t.insert().execute( name="b2g-whitelist", product="b2g", version="2.5", data_version=1, data=""" { "name": "b2g-whitelist", "schema_version": 3000, "whitelist": [ { "imei": "000000000000000" }, { "imei": "000000000000001" }, { "imei": "000000000000002" } ] } """, ) dbo.releases.t.insert().execute( name="foxfood-whitelisted", product="b2g", version="2.5", data_version=1, data=""" { "name": "foxfood-whitelisted", "schema_version": 1, "extv": "2.5", "hashFunction": "sha512", "platforms": { "p": { "buildID": "25", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://a.com/secrets" } } } } } } """, ) dbo.releases.t.insert().execute( name="foxfood-fallback", product="b2g", version="2.5", data_version=1, data=""" { "name": "foxfood-fallback", "schema_version": 1, "extv": "2.5", "hashFunction": "sha512", "platforms": { "p": { "buildID": "25", "locales": { "l": { "complete": { "filesize": 22, "from": "*", "hashValue": "23", "fileUrl": "http://a.com/public" } } } } } } """, )