diff --git a/DESCRIPTION.md b/DESCRIPTION.md index 80538b54a..46b6d551c 100644 --- a/DESCRIPTION.md +++ b/DESCRIPTION.md @@ -8,6 +8,9 @@ Source code is also available at: https://github.com/snowflakedb/snowflake-conne # Release Notes +- v2.8.3(Unreleased) + - Fixed a bug where the permission of the file downloaded via GET command is changed + - v2.8.2(November 18,2022) - Improved performance of OCSP response caching diff --git a/src/snowflake/connector/encryption_util.py b/src/snowflake/connector/encryption_util.py index cbdd06f8f..43fa5ee7f 100644 --- a/src/snowflake/connector/encryption_util.py +++ b/src/snowflake/connector/encryption_util.py @@ -18,6 +18,7 @@ from .compat import PKCS5_OFFSET, PKCS5_PAD, PKCS5_UNPAD from .constants import UTF8, EncryptionMetadata, MaterialDescriptor, kilobyte +from .util_text import random_string block_size = int(algorithms.AES.block_size / 8) # in bytes @@ -238,12 +239,13 @@ def decrypt_file( Returns: The decrypted file's location. """ - temp_output_fd, temp_output_file = tempfile.mkstemp( - text=False, dir=tmp_dir, prefix=os.path.basename(in_filename) + "#" - ) + temp_output_file = f"{os.path.basename(in_filename)}#{random_string()}" + if tmp_dir: + temp_output_file = os.path.join(tmp_dir, temp_output_file) + logger.debug("encrypted file: %s, tmp file: %s", in_filename, temp_output_file) with open(in_filename, "rb") as infile: - with os.fdopen(temp_output_fd, "wb") as outfile: + with open(temp_output_file, "wb") as outfile: SnowflakeEncryptionUtil.decrypt_stream( metadata, encryption_material, infile, outfile, chunk_size ) diff --git a/test/integ/test_put_get.py b/test/integ/test_put_get.py index 551010b13..2bdb9955c 100644 --- a/test/integ/test_put_get.py +++ b/test/integ/test_put_get.py @@ -671,3 +671,27 @@ def test_get_empty_file(tmp_path, conn_cnx): with pytest.raises(OperationalError, match=".*the file does not exist.*$"): cur.execute(f"GET @{stage_name}/foo.csv file://{tmp_path}") assert not empty_file.exists() + + +@pytest.mark.skipolddriver +def test_get_file_permission(tmp_path, conn_cnx): + test_file = tmp_path / "data.csv" + test_file.write_text("1,2,3\n") + stage_name = random_string(5, "test_get_empty_file_") + with conn_cnx() as cnx: + with cnx.cursor() as cur: + cur.execute(f"create temporary stage {stage_name}") + filename_in_put = str(test_file).replace("\\", "/") + cur.execute( + f"PUT 'file://{filename_in_put}' @{stage_name}", + ) + + cur.execute(f"GET @{stage_name}/data.csv file://{tmp_path}") + # get the default mask, usually it is 0o022 + default_mask = os.umask(0) + os.umask(default_mask) + # files by default are given the permission 644 (Octal) + # umask is for denial, we need to negate + assert ( + oct(os.stat(test_file).st_mode)[-3:] == oct(0o666 & ~default_mask)[-3:] + )