From 69e489cf088975fbd4dc3cc2b0ac704d7e8aa872 Mon Sep 17 00:00:00 2001 From: Scott Collins Date: Tue, 16 Nov 2021 11:12:33 -0800 Subject: [PATCH] Updated TransactionOnDisk.write() to ensure that any files or directories created during the process have appropriate group read/write permissions set --- .../core/outputs/transaction_on_disk.py | 94 ++++++++++++++----- 1 file changed, 69 insertions(+), 25 deletions(-) diff --git a/src/pds_doi_service/core/outputs/transaction_on_disk.py b/src/pds_doi_service/core/outputs/transaction_on_disk.py index 70371f70..9d2d12b2 100644 --- a/src/pds_doi_service/core/outputs/transaction_on_disk.py +++ b/src/pds_doi_service/core/outputs/transaction_on_disk.py @@ -19,57 +19,95 @@ import requests from pds_doi_service.core.util.config_parser import DOIConfigUtil from pds_doi_service.core.util.general_util import get_logger -from pds_doi_service.core.util.node_util import NodeUtil -logger = get_logger("pds_doi_service.core.outputs.transaction_logger") +logger = get_logger(__name__) class TransactionOnDisk: """ This class provides services to write a transaction from an action - (reserve, draft or release) to disk. + (reserve, update or release) to disk. """ m_doi_config_util = DOIConfigUtil() - m_node_util = NodeUtil() - m_doi_database = None def __init__(self): self._config = self.m_doi_config_util.get_config() def write(self, node_id, update_time, input_ref=None, output_content=None, output_content_type=None): """ - Write a the input and output products from a transaction to disk. - The location of the written files is returned. + Write the input and output products from a transaction to disk. + The location of the written files is returned. All directories and files + created will have both user and group read/write permissions set accordingly. + + Parameters + ---------- + node_id : str + PDS Node ID to associate with the transaction to disk. Determines + which subdirectory the input/output is written to. + update_time : datetime.datetime + datetime object corresponding to the time of the original transaction. + Forms part of the path where the transaction is written to on disk. + input_ref : str, optional + Path to the input file or directory to associate with the transaction. + Determines the input file(s) copied to the transaction history. + output_content : str, optional + The output label content to associate to with the transaction. + Determines the contents of the output file copied to the transaction history. + output_content_type : str, optional + The content type of output_content. Should be one of "xml" or "json". + + Returns + ------- + final_output_dir : str + Path to the directory in the transaction history created by this + method. The path has the following form: + + // + + Where is set in the INI config, + is the value provided for node_id, and is the provided + update_time as an isoformat string. + """ transaction_dir = self._config.get("OTHER", "transaction_dir") logger.debug(f"transaction_dir {transaction_dir}") # Create the local transaction history directory, if necessary. final_output_dir = os.path.join(transaction_dir, node_id, update_time.isoformat()) - os.makedirs(final_output_dir, exist_ok=True) + + # Set up the appropriate umask in-case os.makedirs needs to create any + # intermediate parent directories (its mask arg only affects the created leaf directory) + prev_umask = os.umask(0o0002) + + # Create the new transaction history directory with group-rw enabled + os.makedirs(final_output_dir, exist_ok=True, mode=0o0775) if input_ref: - input_content_type = os.path.splitext(input_ref)[-1] + if os.path.isdir(input_ref): + # Copy the input files, but do not preserve their permissions so + # the umask we set above takes precedence + copy_tree(input_ref, os.path.join(final_output_dir, "input"), preserve_mode=False) + else: + input_content_type = os.path.splitext(input_ref)[-1] - # Write input file with provided content. - # Note that the file name is always 'input' plus the extension based - # on the content_type (input.xml or input.csv or input.xlsx) - full_input_name = os.path.join(final_output_dir, "input" + input_content_type) + # Write input file with provided content. + # Note that the file name is always 'input' plus the extension based + # on the content_type (input.xml or input.csv or input.xlsx) + full_input_name = os.path.join(final_output_dir, "input" + input_content_type) - # If the provided content is actually a file name, we copy it, - # otherwise write it to external file using full_input_name as name. - if os.path.isfile(input_ref): - shutil.copy2(input_ref, full_input_name) - elif os.path.isdir(input_ref): - copy_tree(input_ref, full_input_name) - else: # remote resource - r = requests.get(input_ref, allow_redirects=True) + if os.path.isfile(input_ref): + shutil.copy2(input_ref, full_input_name) + else: # remote resource + r = requests.get(input_ref, allow_redirects=True) - with open(full_input_name, "wb") as outfile: - outfile.write(r.content) + with open(full_input_name, "wb") as outfile: + outfile.write(r.content) - r.close() + r.close() + + # Set up permissions for copied input + os.chmod(full_input_name, 0o0664) # Write output file with provided content # The extension of the file is determined by the provided content type @@ -79,6 +117,12 @@ def write(self, node_id, update_time, input_ref=None, output_content=None, outpu with open(full_output_name, "w") as outfile: outfile.write(output_content) - logger.info(f"transaction files saved in {final_output_dir}") + # Set up permissions for copied output + os.chmod(full_output_name, 0o0664) + + logger.info(f"Transaction files saved to {final_output_dir}") + + # Restore the previous umask + os.umask(prev_umask) return final_output_dir