-
Notifications
You must be signed in to change notification settings - Fork 1
π¨ Palette: Add validation to interactive prompts #127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -247,15 +247,37 @@ | |
|
|
||
| return True | ||
|
|
||
| def validate_profile_id(profile_id: str) -> bool: | ||
| def validate_profile_id(profile_id: str, log_errors: bool = True) -> bool: | ||
Check warningCode scanning / Pylint (reported by Codacy) Missing function docstring Warning
Missing function docstring
|
||
| if not re.match(r"^[a-zA-Z0-9_-]+$", profile_id): | ||
| log.error("Invalid profile ID format (contains unsafe characters)") | ||
| if log_errors: | ||
| log.error("Invalid profile ID format (contains unsafe characters)") | ||
| return False | ||
| if len(profile_id) > 64: | ||
| log.error("Invalid profile ID length (max 64 chars)") | ||
| if log_errors: | ||
| log.error("Invalid profile ID length (max 64 chars)") | ||
| return False | ||
| return True | ||
|
|
||
| def get_valid_input(prompt: str, validator: callable, error_msg: str, is_password: bool = False) -> str: | ||
Check warningCode scanning / Pylintpython3 (reported by Codacy) Line too long (104/100) Warning
Line too long (104/100)
Check warningCode scanning / Pylint (reported by Codacy) Line too long (104/100) Warning
Line too long (104/100)
|
||
| """ | ||
| Prompts the user for input until it passes validation. | ||
| """ | ||
| import getpass | ||
| while True: | ||
| if is_password: | ||
| value = getpass.getpass(prompt).strip() | ||
| else: | ||
| value = input(prompt).strip() | ||
Check noticeCode scanning / Bandit (reported by Codacy) The input method in Python 2 will read from standard input, evaluate and run the resulting string as python source code. This is similar, though in many ways worse, then using eval. On Python 2, use raw_input instead, input is safe in Python 3. Note
The input method in Python 2 will read from standard input, evaluate and run the resulting string as python source code. This is similar, though in many ways worse, then using eval. On Python 2, use raw_input instead, input is safe in Python 3.
|
||
|
|
||
| if not value: | ||
| print(f"{Colors.FAIL}β Value cannot be empty.{Colors.ENDC}") | ||
| continue | ||
|
|
||
| if validator(value): | ||
| return value | ||
|
|
||
| print(f"{Colors.FAIL}β {error_msg}{Colors.ENDC}") | ||
|
|
||
| def is_valid_rule(rule: str) -> bool: | ||
| """ | ||
| Validates that a rule is safe to use. | ||
|
|
@@ -376,9 +398,9 @@ | |
| log.critical(f"{Colors.FAIL}β Authentication Failed: The API Token is invalid.{Colors.ENDC}") | ||
| log.critical(f"{Colors.FAIL} Please check your token at: https://controld.com/account/manage-account{Colors.ENDC}") | ||
| elif code == 403: | ||
| log.critical(f"{Colors.FAIL}π« Access Denied: Token lacks permission for Profile {profile_id}.{Colors.ENDC}") | ||
Check failureCode scanning / CodeQL Clear-text logging of sensitive information High
This expression logs
sensitive data (password) Error loading related location Loading |
||
| elif code == 404: | ||
| log.critical(f"{Colors.FAIL}π Profile Not Found: The ID '{profile_id}' does not exist.{Colors.ENDC}") | ||
Check failureCode scanning / CodeQL Clear-text logging of sensitive information High
This expression logs
sensitive data (password) Error loading related location Loading |
||
| log.critical(f"{Colors.FAIL} Please verify the Profile ID from your Control D Dashboard URL.{Colors.ENDC}") | ||
| else: | ||
| log.error(f"API Access Check Failed ({code}): {e}") | ||
|
|
@@ -854,7 +876,7 @@ | |
| return success_count == len(folder_data_list) | ||
|
|
||
| except Exception as e: | ||
| log.error(f"Unexpected error during sync for profile {profile_id}: {sanitize_for_log(e)}") | ||
Check failureCode scanning / CodeQL Clear-text logging of sensitive information High
This expression logs
sensitive data (password) Error loading related location Loading |
||
| return False | ||
|
|
||
| # --------------------------------------------------------------------------- # | ||
|
|
@@ -866,7 +888,7 @@ | |
| parser.add_argument("--folder-url", action="append", help="Folder JSON URL(s)", default=None) | ||
| parser.add_argument("--dry-run", action="store_true", help="Plan only") | ||
| parser.add_argument("--no-delete", action="store_true", help="Do not delete existing folders") | ||
| parser.add_argument("--plan-json", help="Write plan to JSON file", default=None) | ||
Check warningCode scanning / Prospector (reported by Codacy) Import outside toplevel (getpass) (import-outside-toplevel) Warning
Import outside toplevel (getpass) (import-outside-toplevel)
Check warningCode scanning / Pylintpython3 (reported by Codacy) Import outside toplevel (getpass) Warning
Import outside toplevel (getpass)
|
||
| return parser.parse_args() | ||
|
|
||
| def main(): | ||
|
|
@@ -881,17 +903,34 @@ | |
| if not profile_ids: | ||
| print(f"{Colors.CYAN}βΉ Profile ID is missing.{Colors.ENDC}") | ||
| print(f"{Colors.CYAN} You can find this in the URL of your profile in the Control D Dashboard.{Colors.ENDC}") | ||
| p_input = input(f"{Colors.BOLD}Enter Control D Profile ID:{Colors.ENDC} ").strip() | ||
| if p_input: | ||
| profile_ids = [p.strip() for p in p_input.split(",") if p.strip()] | ||
|
|
||
| def validate_profiles(text: str) -> bool: | ||
Check warningCode scanning / Pylint (reported by Codacy) Missing function docstring Warning
Missing function docstring
|
||
| ids = [p.strip() for p in text.split(",") if p.strip()] | ||
| if not ids: return False | ||
Check warningCode scanning / Pylintpython3 (reported by Codacy) More than one statement on a single line Warning
More than one statement on a single line
Check warningCode scanning / Pylint (reported by Codacy) More than one statement on a single line Warning
More than one statement on a single line
|
||
| return all(validate_profile_id(pid, log_errors=False) for pid in ids) | ||
|
|
||
| p_input = get_valid_input( | ||
| f"{Colors.BOLD}Enter Control D Profile ID:{Colors.ENDC} ", | ||
| validate_profiles, | ||
| "Invalid Profile ID(s). Must be alphanumeric (max 64 chars)." | ||
| ) | ||
| profile_ids = [p.strip() for p in p_input.split(",") if p.strip()] | ||
|
|
||
| if not TOKEN: | ||
| print(f"{Colors.CYAN}βΉ API Token is missing.{Colors.ENDC}") | ||
| print(f"{Colors.CYAN} You can generate one at: https://controld.com/account/manage-account{Colors.ENDC}") | ||
| import getpass | ||
| t_input = getpass.getpass(f"{Colors.BOLD}Enter Control D API Token:{Colors.ENDC} ").strip() | ||
| if t_input: | ||
| TOKEN = t_input | ||
|
|
||
| def validate_token(text: str) -> bool: | ||
|
||
| # Basic sanity check | ||
| return len(text) > 8 | ||
|
|
||
| t_input = get_valid_input( | ||
| f"{Colors.BOLD}Enter Control D API Token:{Colors.ENDC} ", | ||
| validate_token, | ||
| "Token seems too short. Please check your API token.", | ||
| is_password=True | ||
| ) | ||
| TOKEN = t_input | ||
|
|
||
| if not profile_ids and not args.dry_run: | ||
| log.error("PROFILE missing and --dry-run not set. Provide --profiles or set PROFILE env.") | ||
|
|
@@ -925,7 +964,7 @@ | |
| }) | ||
| continue | ||
|
|
||
| log.info("Starting sync for profile %s", profile_id) | ||
Check failureCode scanning / CodeQL Clear-text logging of sensitive information High
This expression logs
sensitive data (password) Error loading related location Loading |
||
| status = sync_profile( | ||
| profile_id, | ||
| folder_urls, | ||
|
|
@@ -1015,11 +1054,11 @@ | |
| status_color = Colors.GREEN if res['success'] else Colors.FAIL | ||
|
|
||
| print( | ||
| f"{res['profile']:<{profile_col_width}} | " | ||
| f"{res['folders']:>10} | " | ||
| f"{res['rules']:>10,} | " | ||
| f"{res['duration']:>9.1f}s | " | ||
| f"{status_color}{res['status_label']:<15}{Colors.ENDC}" | ||
Check failureCode scanning / CodeQL Clear-text logging of sensitive information High
This expression logs
sensitive data (password) Error loading related location Loading |
||
| ) | ||
| total_folders += res['folders'] | ||
| total_rules += res['rules'] | ||
|
|
@@ -1045,12 +1084,12 @@ | |
| total_status_color = Colors.GREEN if all_success else Colors.FAIL | ||
|
|
||
| print( | ||
| f"{Colors.BOLD}" | ||
| f"{'TOTAL':<{profile_col_width}} | " | ||
| f"{total_folders:>10} | " | ||
| f"{total_rules:>10,} | " | ||
| f"{total_duration:>9.1f}s | " | ||
| f"{total_status_color}{total_status_text:<15}{Colors.ENDC}" | ||
Check failureCode scanning / CodeQL Clear-text logging of sensitive information High
This expression logs
sensitive data (password) Error loading related location Loading |
||
| ) | ||
| print("=" * table_width + "\n") | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.