def check_previous_upload(self, token): """Do API call and check for the files in the DB.""" LOG.debug("Checking if files have been previously uploaded.") # Get files from db files = list(x for x in self.data) try: response = requests.get( DDSEndpoint.FILE_MATCH, params={"project": self.project}, headers=token, json=files, timeout=DDSEndpoint.TIMEOUT, ) except requests.exceptions.RequestException as err: LOG.warning(err) raise SystemExit( "Failed to check previous upload status" + ( ": The database seems to be down." if isinstance(err, requests.exceptions.ConnectionError) else "." ) ) from err if not response.ok: message = "Failed getting information about previously uploaded files" if response.status_code == http.HTTPStatus.INTERNAL_SERVER_ERROR: raise exceptions.ApiResponseError(message=f"{message}: {response.reason}") raise exceptions.DDSCLIException(message=f"{message}: {response.json().get('message')}") try: files_in_db = response.json() except simplejson.JSONDecodeError as err: LOG.warning(err) raise SystemExit from err # API failure if "files" not in files_in_db: dds_cli.utils.console.print( "\n:warning-emoji: Files not returned from API. :warning-emoji:\n" ) os._exit(1) LOG.debug("Previous upload check finished.") return dict() if files_in_db["files"] is None else files_in_db["files"]
def __get_key(self, private: bool = False): """Get public key for project.""" key_type = "private" if private else "public" # Get key from API try: response = requests.get( DDSEndpoint.PROJ_PRIVATE if private else DDSEndpoint.PROJ_PUBLIC, params={"project": self.project}, headers=self.token, timeout=DDSEndpoint.TIMEOUT, ) except requests.exceptions.RequestException as err: LOG.fatal(str(err)) raise SystemExit("Failed to get cloud information" + (": The database seems to be down." if isinstance( err, requests.exceptions.ConnectionError ) else ".")) from err if not response.ok: message = "Failed getting key from DDS API" if response.status_code == http.HTTPStatus.INTERNAL_SERVER_ERROR: raise exceptions.ApiResponseError( message=f"{message}: {response.reason}") raise exceptions.DDSCLIException( message=f"{message}: {response.json().get('message')}") # Get key from response try: project_public = response.json() except simplejson.JSONDecodeError as err: LOG.fatal(str(err)) raise SystemExit from err if key_type not in project_public: utils.console.print( "\n:no_entry_sign: Project access denied: No {key_type} key. :no_entry_sign:\n" ) os._exit(1) return project_public[key_type]
def __get_s3_info(project_id, token): """Get information required to connect to cloud.""" # Perform request to API try: response = requests.get( DDSEndpoint.S3KEYS, params={"project": project_id}, headers=token, timeout=DDSEndpoint.TIMEOUT, ) except requests.exceptions.RequestException as err: LOG.warning(err) raise SystemExit("Failed to get cloud information" + (": The database seems to be down." if isinstance( err, requests.exceptions.ConnectionError ) else ".")) from err # Error if not response.ok: message = f"Connection error: {response.text}" raise exceptions.ApiResponseError(message) # TODO: Change # Get s3 info s3info = utils.get_json_response(response=response) safespring_project, keys, url, bucket = ( s3info.get("safespring_project"), s3info.get("keys"), s3info.get("url"), s3info.get("bucket"), ) if None in [safespring_project, keys, url, bucket]: raise SystemExit( "Missing safespring information in response.") # TODO: change return safespring_project, keys, url, bucket
def __authenticate_user(self): """Authenticates the username and password via a call to the API.""" LOG.debug("Starting authentication on the API.") if self.no_prompt: raise exceptions.AuthenticationError(message=( "Authentication not possible when running with --no-prompt. " "Please run the `dds auth login` command and authenticate interactively." )) username = rich.prompt.Prompt.ask("DDS username") password = getpass.getpass(prompt="DDS password: "******"": raise exceptions.AuthenticationError( message="Non-empty password needed to be able to authenticate." ) try: response = requests.get( dds_cli.DDSEndpoint.ENCRYPTED_TOKEN, auth=(username, password), timeout=dds_cli.DDSEndpoint.TIMEOUT, ) response_json = response.json() except requests.exceptions.RequestException as err: raise exceptions.ApiRequestError(message=( "Failed to authenticate user" + (": The database seems to be down." if isinstance( err, requests.exceptions.ConnectionError) else ".") )) from err except simplejson.JSONDecodeError as err: raise dds_cli.exceptions.ApiResponseError(message=str(err)) if not response.ok: if response.status_code == 401: raise exceptions.AuthenticationError( "Authentication failed, incorrect username and/or password." ) raise dds_cli.exceptions.ApiResponseError( message= f"API returned an error: {response_json.get('message', 'Unknown Error!')}" ) # Token received from API needs to be completed with a mfa timestamp partial_auth_token = response_json.get("token") # Verify 2fa email token LOG.info("Please enter the one-time authentication code sent " "to your email address (leave empty to exit):") done = False while not done: entered_one_time_code = rich.prompt.Prompt.ask( "Authentication one-time code") if entered_one_time_code == "": raise exceptions.AuthenticationError( message= "Exited due to no one-time authentication code entered.") if not entered_one_time_code.isdigit(): LOG.info( "Please enter a valid one-time code. It should consist of only digits." ) continue if len(entered_one_time_code) != 8: LOG.info( "Please enter a valid one-time code. It should consist of 8 digits " f"(you entered {len(entered_one_time_code)} digits).") continue try: response = requests.get( dds_cli.DDSEndpoint.SECOND_FACTOR, headers={"Authorization": f"Bearer {partial_auth_token}"}, json={"HOTP": entered_one_time_code}, timeout=dds_cli.DDSEndpoint.TIMEOUT, ) response_json = response.json() except requests.exceptions.RequestException as err: raise exceptions.ApiRequestError(message=( "Failed to authenticate with second factor" + (": The database seems to be down." if isinstance( err, requests.exceptions.ConnectionError) else ".") )) from err if response.ok: # Step out of the while-loop done = True if not response.ok: message = response_json.get("message", "Unexpected error!") if response.status_code == 401: try_again = rich.prompt.Confirm.ask( "Second factor authentication failed, would you like to try again?" ) if not try_again: raise exceptions.AuthenticationError( message="Exited due to user choice.") else: raise exceptions.ApiResponseError(message=message) # Get token from response token = response_json.get("token") if not token: raise exceptions.AuthenticationError( message="Missing token in authentication response.") LOG.debug(f"User {username} granted access to the DDS") return token
def list_projects(self, sort_by="Updated"): """Get a list of project(s) the user is involved in.""" # Get projects from API try: response = requests.get( DDSEndpoint.LIST_PROJ, headers=self.token, json={"usage": self.show_usage}, timeout=DDSEndpoint.TIMEOUT, ) except requests.exceptions.RequestException as err: raise exceptions.ApiRequestError( message=( "Failed to get list of projects" + ( ": The database seems to be down." if isinstance(err, requests.exceptions.ConnectionError) else "." ) ) ) # Check response if not response.ok: raise exceptions.APIError(f"Failed to get any projects: {response.text}") # Get result from API try: resp_json = response.json() except simplejson.JSONDecodeError as err: raise exceptions.APIError(f"Could not decode JSON response: {err}") # Cancel if user not involved in any projects usage_info = resp_json.get("total_usage") total_size = resp_json.get("total_size") project_info = resp_json.get("project_info") always_show = resp_json.get("always_show", False) if not project_info: raise exceptions.NoDataError("No project info was retrieved. No files to list.") for project in project_info: try: last_updated = pytz.timezone("UTC").localize( datetime.datetime.strptime(project["Last updated"], "%a, %d %b %Y %H:%M:%S GMT") ) except ValueError as err: raise exceptions.ApiResponseError( f"Time zone mismatch: Incorrect zone '{project['Last updated'].split()[-1]}'" ) else: project["Last updated"] = last_updated.astimezone(tzlocal.get_localzone()).strftime( "%a, %d %b %Y %H:%M:%S %Z" ) # Sort projects according to chosen or default, first ID sorted_projects = self.__sort_projects(projects=project_info, sort_by=sort_by) if not self.json: self.__print_project_table(sorted_projects, usage_info, total_size, always_show) # Return the list of projects return sorted_projects
def get_status(self, show_history): """Get current status and status history of the project.""" try: response = requests.get( DDSEndpoint.UPDATE_PROJ_STATUS, headers=self.token, params={"project": self.project}, json={"history": show_history}, timeout=DDSEndpoint.TIMEOUT, ) except requests.exceptions.RequestException as err: raise exceptions.ApiRequestError( message=( "Failed to get project status" + ( ": The database seems to be down." if isinstance(err, requests.exceptions.ConnectionError) else "." ) ) ) # Check response if not response.ok: raise exceptions.APIError(f"Failed to get any projects: {response.text}") # Get result from API try: resp_json = response.json() except simplejson.JSONDecodeError as err: raise exceptions.APIError(f"Could not decode JSON response: {err}") else: current_status = resp_json.get("current_status") current_deadline = resp_json.get("current_deadline") status_out = f"Current status of {self.project}: {current_status}" deadline_out = "" if current_deadline: try: date = pytz.timezone("UTC").localize( datetime.datetime.strptime(current_deadline, "%a, %d %b %Y %H:%M:%S GMT") ) except ValueError as err: raise exceptions.ApiResponseError( f"Time zone mismatch: Incorrect zone '{current_deadline.split()[-1]}'" ) else: current_deadline = date.astimezone(tzlocal.get_localzone()).strftime( "%a, %d %b %Y %H:%M:%S %Z" ) deadline_out = f" with deadline {current_deadline}" dds_cli.utils.console.print(f"{status_out}{deadline_out}") if show_history: history = "Status history \n" for row in resp_json.get("history"): try: date = pytz.timezone("UTC").localize( datetime.datetime.strptime(row[1], "%a, %d %b %Y %H:%M:%S GMT") ) except ValueError as err: raise exceptions.ApiResponseError( f"Time zone mismatch: Incorrect zone '{row[1].split()[-1]}'" ) else: row[1] = date.astimezone(tzlocal.get_localzone()).strftime( "%a, %d %b %Y %H:%M:%S %Z" ) history += ", ".join(list(row)) + " \n" LOG.info(history)