def delete_database(data, backup_id, confirm=False, recreate=False): common.verify_token(data) # Check if the backup exists result = fetch_backups(data, [backup_id], "get") if result is None or len(result) == 0: return if not confirm: # Confirm deletion with user name = next(iter(result[0])) message = 'Delete database ' + str(backup_id) message += ' belonging to "' + name + '"?' options = '[y/N]:' agree = input(message + ' ' + options) if agree.lower() not in ["y", "yes"]: common.log_output("Database not deleted", True) return baseurl = common.create_baseurl( data, "/api/v1/backup/" + str(backup_id) + "/deletedb") cookies = common.create_cookies(data) headers = common.create_headers(data) verify = data.get("server", {}).get("verify", True) r = requests.post(baseurl, headers=headers, cookies=cookies, verify=verify) common.check_response(data, r.status_code) if r.status_code != 200: common.log_output("Error deleting database", True, r.status_code) return common.log_output("Database deleted", True, 200) if recreate: repair_database(data, backup_id)
def call_backup_subcommand(data, url, fail_message, success_message): common.verify_token(data) baseurl = common.create_baseurl(data, url) cookies = common.create_cookies(data) headers = common.create_headers(data) verify = data.get("server", {}).get("verify", True) r = requests.post(baseurl, headers=headers, cookies=cookies, verify=verify) common.check_response(data, r.status_code) if r.status_code != 200: common.log_output(fail_message, True, r.status_code) return common.log_output(success_message, True, 200)
def abort_task(data, task_id): common.verify_token(data) path = "/api/v1/task/" + str(task_id) + "/abort" baseurl = common.create_baseurl(data, path) cookies = common.create_cookies(data) headers = common.create_headers(data) verify = data.get("server", {}).get("verify", True) r = requests.post(baseurl, headers=headers, cookies=cookies, verify=verify) common.check_response(data, r.status_code) if r.status_code != 200: common.log_output("Error aborting task ", True, r.status_code) return common.log_output("Task aborted", True, 200)
def run_backup(data, backup_id): common.verify_token(data) path = "/api/v1/backup/" + str(backup_id) + "/run" baseurl = common.create_baseurl(data, path) cookies = common.create_cookies(data) headers = common.create_headers(data) verify = data.get("server", {}).get("verify", True) r = requests.post(baseurl, headers=headers, cookies=cookies, verify=verify) common.check_response(data, r.status_code) if r.status_code != 200: common.log_output("Error scheduling backup ", True, r.status_code) return common.log_output("Backup scheduled", True, 200)
def validate_database_exists(data, db_path): common.verify_token(data) # api/v1/filesystem/validate baseurl = common.create_baseurl(data, "/api/v1/filesystem/validate") cookies = common.create_cookies(data) headers = common.create_headers(data) payload = {'path': db_path} verify = data.get("server", {}).get("verify", True) r = requests.post(baseurl, headers=headers, params=payload, cookies=cookies, verify=verify) common.check_response(data, r.status_code) if r.status_code != 200: return False return True
def import_backup(data, import_file, backup_id=None, import_meta=None): # Don't load nonexisting files if os.path.isfile(import_file) is False: common.log_output(import_file + " not found", True) return # Load the import file with io.open(import_file, 'r', encoding="UTF-8") as file_handle: extension = splitext(import_file)[1] if extension.lower() in ['.yml', '.yaml']: try: backup_config = yaml.safe_load(file_handle) except yaml.YAMLError: common.log_output("Failed to load file as YAML", True) return elif extension.lower() == ".json": try: backup_config = json.load(file_handle) except Exception: common.log_output("Failed to load file as JSON", True) return # Determine if we're importing a new backup or updating an existing backup if backup_id is not None: return update_backup(data, backup_id, backup_config, import_meta) common.verify_token(data) # Strip metadata if requsted if import_meta is None or import_meta is not True: backup_config["Backup"]["Metadata"] = {} # Prepare the imported JSON object as a string backup_config = json.dumps(backup_config, default=str) # Upload our JSON string as a file with requests files = { 'config': ('backup_config.json', backup_config, 'application/json') } # Will eventually support passphrase encrypted configs, but we will # need to decrypt them in the client in order to convert them payload = { 'passphrase': '', 'import_metadata': import_meta, 'direct': True } cookies = common.create_cookies(data) baseurl = common.create_baseurl(data, "/api/v1/backups/import", True) verify = data.get("server", {}).get("verify", True) r = requests.post(baseurl, files=files, cookies=cookies, data=payload, verify=verify) common.check_response(data, r.status_code) # Code for extracting error messages posted with inline javascript # and with 200 OK http status code, preventing us from detecting # the error otherwise. try: text = r.text start = text.index("if (rp) { rp('") + 14 end = text.index(", line ") error = text[start:end].replace("\\'", "'") + "." common.log_output(error, True) sys.exit(2) except ValueError: pass if r.status_code != 200: message = "Error importing backup configuration" common.log_output(message, True, r.status_code) sys.exit(2) common.log_output("Backup job created", True, 200)
def login(data, input_url=None, password=None, verify=True, interactive=True, basic_user=None, basic_pass=None): if input_url is None: input_url = "" # Split protocol, url, and port input_url = input_url.replace("/", "").replace("_", "") count = input_url.count(":") protocol = "" url = "" port = "" if count == 2: protocol, url, port = input_url.split(":") elif count == 1 and input_url.index(":") < 6: protocol, url = input_url.split(":") elif count == 1: url, port = input_url.split(":") elif count == 0: url = input_url else: common.log_output("Invalid URL", True) sys.exit(2) # Strip nondigits port = ''.join(re.findall(r'\d+', port)) # Default to config file values for any missing parameters if protocol is None or protocol.lower() not in ["http", "https"]: protocol = data["server"]["protocol"] if url is None or url == "": url = data["server"]["url"] if port is None or port == "": port = data["server"]["port"] # Update config data["server"]["protocol"] = protocol data["server"]["url"] = url data["server"]["port"] = port # Make the login attempt baseurl = common.create_baseurl(data, "") common.log_output("Connecting to " + baseurl + "...", False) r = requests.get(baseurl, allow_redirects=True, verify=verify) common.check_response(data, r.status_code) # Detect if we were redirected to https if "https://" in r.url and protocol != "https": data["server"]["protocol"] = "https" common.log_output("Redirected from http to https", True) # Detect if we're prompted for basic authentication auth_method = r.headers.get('WWW-Authenticate', False) if (auth_method): common.log_output('Basic authentication required...', False) if basic_user is None and interactive: basic_user = input('Basic username: '******'You must provide a basic auth username, --basic-user' common.log_output(message, True) sys.exit(2) if basic_pass is None and interactive: basic_pass = getpass.getpass('Basic password:'******'ascii')) # Create the authorization string basic_auth = "Basic " + secret.decode('utf-8') headers = {"Authorization": basic_auth} r = requests.get(baseurl, verify=verify, headers=headers, allow_redirects=True) common.check_response(data, r.status_code) if r.status_code == 200: common.log_output('Passed basic auth', False) # Update basic auth secret in config file data['authorization'] = basic_auth # Detect if we were prompted to login login_redirect = "/login.html" in r.url if r.status_code == 200 and not login_redirect: common.log_output("OK", False, r.status_code) token = urllib.parse.unquote(r.cookies["xsrf-token"]) elif r.status_code == 200 and login_redirect: password = prompt_password(password, interactive) common.log_output("Getting nonce and salt...", False) baseurl = common.create_baseurl(data, "/login.cgi") headers = common.create_headers(data) payload = {'get-nonce': 1} r = requests.post(baseurl, headers=headers, data=payload, verify=verify) if r.status_code != 200: common.log_output("Error getting salt from server", True, r.status_code) sys.exit(2) salt = r.json()["Salt"] data["nonce"] = urllib.parse.unquote(r.json()["Nonce"]) token = urllib.parse.unquote(r.cookies["xsrf-token"]) common.log_output("Hashing password...", False) salt_password = password.encode() + base64.b64decode(salt) saltedpwd = hashlib.sha256(salt_password).digest() nonce_password = base64.b64decode(data["nonce"]) + saltedpwd noncedpwd = hashlib.sha256(nonce_password).digest() common.log_output("Authenticating... ", False) payload = { "password": base64.b64encode(noncedpwd).decode('utf-8') } cookies = { "xsrf-token": token, "session-nonce": data.get("nonce", "") } r = requests.post(baseurl, headers=headers, data=payload, cookies=cookies, verify=verify) common.check_response(data, r.status_code) if r.status_code == 200: common.log_output("Connected", False, r.status_code) data["session-auth"] = urllib.parse.unquote( r.cookies["session-auth"]) else: message = "Error authenticating against the server" common.log_output(message, True, r.status_code) sys.exit(2) else: message = "Error connecting to server" common.log_output(message, True, r.status_code) sys.exit(2) # Update the config file with provided values data["token"] = token expiration = datetime.datetime.now() + datetime.timedelta(0, 600) data["token_expires"] = expiration data["last_login"] = datetime.datetime.now() common.write_config(data) common.log_output("Login successful", True) return data