|
| 1 | +import os |
| 2 | +import os.path |
| 3 | +import string |
| 4 | +import sys |
| 5 | + |
| 6 | +from flask import current_app, redirect |
| 7 | +from google.oauth2 import service_account |
| 8 | +from google.cloud import storage |
| 9 | +from werkzeug.utils import secure_filename |
| 10 | + |
| 11 | +from CTFd.utils import get_app_config |
| 12 | +from CTFd.utils.uploads import UPLOADERS |
| 13 | +from CTFd.utils.uploads.uploaders import BaseUploader |
| 14 | +from CTFd.utils.encoding import hexencode |
| 15 | + |
| 16 | +# Requires setting GCP_STORE_CRED and GCP_STORE_BUCKET vars |
| 17 | +# and GCP_STORE_CRED should be a json file for our private key creds |
| 18 | +# |
| 19 | +# Should also modify the requirements.in and requiremnts.txt file to have |
| 20 | +# google-cloud-storage==1.33.0 dependency |
| 21 | + |
| 22 | +class GCPUploader(BaseUploader): |
| 23 | + def __init__(self): |
| 24 | + super(BaseUploader, self).__init__() |
| 25 | + self.gcp = storage.Client.from_service_account_json( |
| 26 | + get_app_config('GCP_STORE_CRED')) |
| 27 | + self.bucket = self.gcp.get_bucket(get_app_config("GCP_STORE_BUCKET")) |
| 28 | + |
| 29 | + |
| 30 | + def _clean_filename(self, c): |
| 31 | + if c in string.ascii_letters + string.digits + "-" + "_" + ".": |
| 32 | + return True |
| 33 | + |
| 34 | + def store(self, fileobj, filename): |
| 35 | + blob = self.bucket.blob(filename) |
| 36 | + blob.upload_from_file(fileobj) |
| 37 | + blob.make_public() |
| 38 | + return filename |
| 39 | + |
| 40 | + def upload(self, file_obj, filename): |
| 41 | + filename = filter( |
| 42 | + self._clean_filename, secure_filename(filename).replace(" ", "_") |
| 43 | + ) |
| 44 | + filename = "".join(filename) |
| 45 | + if len(filename) <= 0: |
| 46 | + return False |
| 47 | + |
| 48 | + md5hash = hexencode(os.urandom(16)) |
| 49 | + |
| 50 | + dst = md5hash + "/" + filename |
| 51 | + return self.store(file_obj, dst) |
| 52 | + |
| 53 | + def download(self, filename): |
| 54 | + blob = self.bucket.blob(filename) |
| 55 | + return redirect(blob.public_url) |
| 56 | + |
| 57 | + def delete(self, filename): |
| 58 | + try: |
| 59 | + # Might have an error if it's not there |
| 60 | + blob = self.bucket.blob(filename) |
| 61 | + blob.delete() |
| 62 | + except: |
| 63 | + import traceback |
| 64 | + traceback.print_exc(file=sys.stdout) |
| 65 | + |
| 66 | + def sync(self): |
| 67 | + local_folder = current_app.config.get("UPLOAD_FOLDER") |
| 68 | + for blob in self.bucket.list_blobs(): |
| 69 | + if blob.name.endswith("/") is False: |
| 70 | + local_path = os.path.join(local_folder, blob.name) |
| 71 | + directory = os.path.dirname(local_path) |
| 72 | + if not os.path.exists(directory): |
| 73 | + os.makedirs(directory) |
| 74 | + |
| 75 | + blob.download_to_file(local_path) |
| 76 | + |
| 77 | +def load(app): |
| 78 | + UPLOADERS['gcp'] = GCPUploader |
0 commit comments