commit a09ca92dce752eee8311879fbd568e49bff428b6 Author: poly poly@darkdepths.net Date: Fri Dec 12 17:03:14 2014 +0400
implemented google drive as a provider --- UPLOAD-GOOGLE-DRIVE | 21 ++++ bundles2drive.py | 335 +++++++++++++++++++++++++++++++++++++++++++++++++++ drive.cfg | 9 ++ drive.links | 25 ++++ providers.txt | 4 +- 5 files changed, 392 insertions(+), 2 deletions(-)
diff --git a/UPLOAD-GOOGLE-DRIVE b/UPLOAD-GOOGLE-DRIVE new file mode 100644 index 0000000..c9a7184 --- /dev/null +++ b/UPLOAD-GOOGLE-DRIVE @@ -0,0 +1,21 @@ +1) Clone into the latest version of gettor: + +$ git clone https://github.com/ileiva/gettor.git + +2) Get the PGP key that signs the Tor Browser Bundle + +2) Visit https://console.developers.google.com//start/api?id=drive&credential=cli... and follow OAUTH2 process to get client ID and client secret for 'other' desktop application. + +3) Edit drive.cfg to with new client-id and secret. Leave refresh_token empty. + +4) Install the google drive API python client: + +$ pip install --upgrade google-api-python-client + +5) Run the script: + +$ python bundles2drive.py + +The first time the script is run, you will have to authorize it through a web browser. You will be prompted with a URL. Once that is done, a refresh token will be stored locally so that re-authorzing is unnesaccary. + +The script will then look for files in upload_dir (as specified in drive.cfg) and upload them to google drive. If no errors occur, it will then add formatted links and hash information to the drive.links file. diff --git a/bundles2drive.py b/bundles2drive.py new file mode 100644 index 0000000..6cf9513 --- /dev/null +++ b/bundles2drive.py @@ -0,0 +1,335 @@ +# -*- coding: utf-8 -*- +# +# This file is part of GetTor, a Tor Browser distribution system. +# +# :authors: poly poly@darkdepths.net +# Israel Leiva ilv@riseup.net +# see also AUTHORS file +# +# :copyright: (c) 2008-2014, The Tor Project, Inc. +# (c) 2014, Poly +# (c) 2014, Israel Leiva +# +# :license: This is Free Software. See LICENSE for license information. + +import re +import os +import gnupg +import hashlib +import ConfigParser +import gettor.core + +#import google drive libs +import httplib2 +from apiclient.discovery import build +from apiclient.http import MediaFileUpload +from oauth2client.client import OAuth2WebServerFlow +from oauth2client.client import Credentials + + +def valid_format(file, osys): + """Check for valid bundle format + + Check if the given file has a valid bundle format + (e.g. tor-browser-linux32-3.6.2_es-ES.tar.xz) + + :param: file (string) the name of the file. + :param: osys (string) the OS. + + :return: (boolean) true if the bundle format is valid, false otherwise. + + """ + if(osys == 'windows'): + m = re.search( + 'torbrowser-install-\d.\d.\d_\w\w(-\w\w)?.exe', + file) + elif(osys == 'linux'): + m = re.search( + 'tor-browser-linux\d\d-\d.\d.\d_(\w\w)(-\w\w)?.tar.xz', + file) + elif(osys == 'osx'): + m = re.search( + 'TorBrowser-\d.\d.\d-osx\d\d_(\w\w)(-\w\w)?.dmg', + file) + if m: + return True + else: + return False + + +def get_bundle_info(file, osys): + """Get the os, arch and lc from a bundle string. + + :param: file (string) the name of the file. + :param: osys (string) the OS. + + :raise: ValueError if the bundle doesn't have a valid bundle format. + + :return: (list) the os, arch and lc. + + """ + if(osys == 'windows'): + m = re.search( + 'torbrowser-install-\d.\d.\d_(\w\w)(-\w\w)?.exe', + file) + if m: + lc = m.group(1) + return 'windows', '32/64', lc + else: + raise ValueError("Invalid bundle format %s" % file) + elif(osys == 'linux'): + m = re.search( + 'tor-browser-linux(\d\d)-\d.\d.\d_(\w\w)(-\w\w)?.tar.xz', + file) + if m: + arch = m.group(1) + lc = m.group(2) + return 'linux', arch, lc + else: + raise ValueError("Invalid bundle format %s" % file) + elif(osys == 'osx'): + m = re.search( + 'TorBrowser-\d.\d.\d-osx(\d\d)_(\w\w)(-\w\w)?.dmg', + file) + if m: + os = 'osx' + arch = m.group(1) + lc = m.group(2) + return 'osx', arch, lc + else: + raise ValueError("Invalid bundle format %s" % file) + + +def get_file_sha256(file): + """Get the sha256 of a file. + + :param: file (string) the path of the file. + + :return: (string) the sha256 hash. + + """ + # as seen on the internetz + BLOCKSIZE = 65536 + hasher = hashlib.sha256() + with open(file, 'rb') as afile: + buf = afile.read(BLOCKSIZE) + while len(buf) > 0: + hasher.update(buf) + buf = afile.read(BLOCKSIZE) + return hasher.hexdigest() + + +def upload_files(client, basedir): + """Upload files to Google Drive. + + Looks for tor browser files inside basedir. + + :param: basedir (string) path of the folder with the files to be + uploaded. + :param: client (object) Google Drive object. + + :raise: UploadError if something goes wrong while uploading the + files to Google Drive. All files are uploaded to '/'. + + :return: (dict) the names of the uploaded files as the keys, + and file id as the value + + """ + files = [] + + p = re.compile('.*.tar.xz$') + for name in os.listdir(basedir): + path = os.path.abspath(os.path.join(basedir, name)) + if os.path.isfile(path) and p.match(path)\ + and valid_format(name, 'linux'): + files.append(name) + + p = re.compile('.*.exe$') + for name in os.listdir(basedir): + path = os.path.abspath(os.path.join(basedir, name)) + if os.path.isfile(path) and p.match(path)\ + and valid_format(name, 'windows'): + files.append(name) + + p = re.compile('.*.dmg$') + for name in os.listdir(basedir): + path = os.path.abspath(os.path.join(basedir, name)) + if os.path.isfile(path) and p.match(path)\ + and valid_format(name, 'osx'): + files.append(name) + + # dictionary to store file names and IDs + files_dict = dict() + + for file in files: + asc = "%s.asc" % file + abs_file = os.path.abspath(os.path.join(basedir, file)) + abs_asc = os.path.abspath(os.path.join(basedir, asc)) + + if not os.path.isfile(abs_asc): + # there are some .mar files that don't have .asc, don't upload it + continue + + # upload tor browser installer + file_body = MediaFileUpload(abs_file, resumable=True) + body = { + 'title': file + } + print "Uploading '%s'..." % file + try: + file_data = drive_service.files().insert(body=body, media_body=file_body).execute() + except: + raise UploadError + + # upload signature + asc_body = MediaFileUpload(abs_asc, resumable=True) + asc_head = { + 'title': "%s.asc" % file + } + print "Uploading '%s'..." % asc + try: + asc_data = drive_service.files().insert(body=asc_head, media_body=asc_body).execute() + except: + raise UploadError + + # add filenames and file id to dict + files_dict[file] = file_data['id'] + files_dict[asc] = asc_data['id'] + + return files_dict + +def share_file(service, file_id): + """Make files public + + For a given file-id, sets role 'reader' to 'anyone'. Returns public + link to file. + + :param: file_id (string) + + :return: (string) url to shared file + + """ + permission = { + 'type': "anyone", + 'role': "reader", + 'withLink': True + } + + try: + service.permissions().insert( + fileId=file_id, body=permission).execute() + except errors.HttpError, error: + print('An error occured while sharing: %s' % file_id) + + try: + file = service.files().get(fileId=file_id).execute() + except errors.HttpError, error: + print('Error occured while fetch public link for file: %s' % file_id) + + print("Uploaded to %s" % file['webContentLink']) + return file['webContentLink'] + + +if __name__ == '__main__': + config = ConfigParser.ConfigParser() + config.read('drive.cfg') + + client_id = config.get('app', 'client-id') + app_secret = config.get('app', 'secret') + refresh_token = config.get('app', 'refresh_token') + upload_dir = config.get('general', 'upload_dir') + + # important: this key must be the one that signed the packages + tbb_key = config.get('general', 'tbb_key') + + # requests full access to drive account + OAUTH_SCOPE = 'https://www.googleapis.com/auth/drive' + REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob' + + print "Authenticating..." + + flow = OAuth2WebServerFlow(client_id, app_secret, OAUTH_SCOPE, + redirect_uri=REDIRECT_URI) + + # If no valid token found, need to prompt user. + # this should only occur once + if not refresh_token: + flow.params['access_type'] = 'offline' + flow.params['approval_prompt'] = 'force' + authorize_url = flow.step1_get_authorize_url() + print 'Go to the following link in your browser: ' + authorize_url + code = raw_input('Enter verification code: ').strip() + credentials = flow.step2_exchange(code) + + # oauth2 credentials instance must be stored as json string + config.set('app', 'refresh_token', credentials.to_json()) + with open('drive.cfg', 'wb') as configfile: + config.write(configfile) + else: + # we already have a valid token + credentials = Credentials.new_from_json(refresh_token) + + # authenticate with oauth2 + http = httplib2.Http() + http = credentials.authorize(http) + + # initialize drive instance + drive_service = build('drive', 'v2', http=http) + + + # import key fingerprint + gpg = gnupg.GPG() + key_data = open(tbb_key).read() + import_result = gpg.import_keys(key_data) + fp = import_result.results[0]['fingerprint'] + + # make groups of four characters to make fingerprint more readable + # e.g. 123A 456B 789C 012D 345E 678F 901G 234H 567I 890J + readable = ' '.join(fp[i:i+4] for i in xrange(0, len(fp), 4)) + + try: + uploaded_files = upload_files(drive_service, upload_dir) + # use default config + core = gettor.core.Core('/home/gettor/core.cfg') + + # erase old links + core.create_links_file('Drive', readable) + + # recognize file OS by its extension + p1 = re.compile('.*.tar.xz$') + p2 = re.compile('.*.exe$') + p3 = re.compile('.*.dmg$') + p4 = re.compile('.*.asc$') + + for file in uploaded_files.keys(): + # only run for tor browser installers + if p4.match(file): + continue + asc = "%s.asc" % file + abs_file = os.path.abspath(os.path.join(upload_dir, file)) + abs_asc = os.path.abspath(os.path.join(upload_dir, asc)) + + sha_file = get_file_sha256(abs_file) + + # build links + link_file = share_file(drive_service, + uploaded_files[file]) + link_asc = share_file(drive_service, + uploaded_files["%s.asc" % file]) + + if p1.match(file): + osys, arch, lc = get_bundle_info(file, 'linux') + elif p2.match(file): + osys, arch, lc = get_bundle_info(file, 'windows') + elif p3.match(file): + osys, arch, lc = get_bundle_info(file, 'osx') + + link = "Package (%s-bit): %s\nASC signature (%s-bit): %s\n"\ + "Package SHA256 checksum (%s-bit): %s\n" %\ + (arch, link_file, arch, link_asc, + arch, sha_file) + + # note that you should only upload bundles for supported locales + core.add_link('Drive', osys, lc, link) + except (ValueError, RuntimeError) as e: + print str(e) diff --git a/drive.cfg b/drive.cfg new file mode 100644 index 0000000..b400af0 --- /dev/null +++ b/drive.cfg @@ -0,0 +1,9 @@ +[general] +upload_dir = upload +tbb_key = tbb-key.asc + +[app] +client-id = +secret = +refresh_token = + diff --git a/drive.links b/drive.links new file mode 100644 index 0000000..5f4fa89 --- /dev/null +++ b/drive.links @@ -0,0 +1,25 @@ +[provider] +name = Drive + +[key] +fingerprint = A3C4 F0F9 79CA A22C DBA8 F512 EE8C BC9E 886D DD89 + +[linux] +en = Package (32-bit): https://docs.google.com/uc?id=0B7humfJYX-LjNTJPanBRekI5Slk&export=downlo... + ASC signature (32-bit): https://docs.google.com/uc?id=0B7humfJYX-LjTXVONkhvZ1VRY2c&export=downlo... + Package SHA256 checksum (32-bit): 03a4ecab9ffb4b579c9d03a0f800d9bcd4446b49eff21eb3e1db255fa9d9b930, + Package (64-bit): https://docs.google.com/uc?id=0B7humfJYX-LjbXp2MUh5RmxTa28&export=downlo... + ASC signature (64-bit): https://docs.google.com/uc?id=0B7humfJYX-LjNTVZVnJUTnNCOEk&export=downlo... + Package SHA256 checksum (64-bit): 03a4ecab9ffb4b579c9d03a0f800d9bcd4446b49eff21eb3e1db255fa9d9b930 + + +[windows] +en = Package (32/64-bit): https://docs.google.com/uc?id=0B7humfJYX-LjakM4a21iRlQzQVE&export=downlo... + ASC signature (32/64-bit): https://docs.google.com/uc?id=0B7humfJYX-LjZy1FLVpTLW9INFU&export=downlo... + Package SHA256 checksum (32/64-bit): 21e54d964366e8a67f379ed402007f59cb3aebfddbc3a30dacc3ac64171145a6 + +[osx] +en = Package (32-bit): https://docs.google.com/uc?id=0B7humfJYX-LjenRobzJtcWw0UnM&export=downlo... + ASC signature (32-bit): https://docs.google.com/uc?id=0B7humfJYX-LjZ0R4Z0I1ZlBRUHc&export=downlo... + Package SHA256 checksum (32-bit): dd86727270ba236d85673194782b4cc9860bd886e4abdf2dfc6d716dabddb68f + diff --git a/providers.txt b/providers.txt index 0f57719..2e8f4cd 100644 --- a/providers.txt +++ b/providers.txt @@ -1,10 +1,10 @@ ================================== CURRENT ====================================
* Dropbox: Implemented. +* Google Drive: Implemented.
-================================== TO BE IMPLEMENTED ========================= +============================= TO BE IMPLEMENTED ===============================
-* Google Drive: Not implemented yet.
================================== IDEAS =====================================