-
Notifications
You must be signed in to change notification settings - Fork 1
🎨 Palette: Interactive input validation & cleanup #133
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 | ||||
|---|---|---|---|---|---|---|
|
|
@@ -23,9 +23,10 @@ | |||||
| import concurrent.futures | ||||||
| import ipaddress | ||||||
| import socket | ||||||
| import getpass | ||||||
| from functools import lru_cache | ||||||
| from urllib.parse import urlparse | ||||||
| from typing import Dict, List, Optional, Any, Set, Sequence | ||||||
| from typing import Dict, List, Optional, Any, Set, Sequence, Callable | ||||||
|
|
||||||
| import httpx | ||||||
| from dotenv import load_dotenv | ||||||
|
|
@@ -140,6 +141,25 @@ | |||||
| return v | ||||||
|
|
||||||
|
|
||||||
| def get_validated_input( | ||||||
| prompt: str, | ||||||
| validator: Callable[[str], bool], | ||||||
Check warningCode scanning / Pylint (reported by Codacy) Wrong hanging indentation before block (add 4 spaces). Warning
Wrong hanging indentation before block (add 4 spaces).
|
||||||
| error_msg: str, | ||||||
Check warningCode scanning / Pylint (reported by Codacy) Wrong hanging indentation before block (add 4 spaces). Warning
Wrong hanging indentation before block (add 4 spaces).
|
||||||
| is_password: bool = False | ||||||
Check warningCode scanning / Pylint (reported by Codacy) Wrong hanging indentation before block (add 4 spaces). Warning
Wrong hanging indentation before block (add 4 spaces).
|
||||||
| ) -> str: | ||||||
| """Prompts for input until the validator returns True.""" | ||||||
| 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 validator(value): | ||||||
| return value | ||||||
|
|
||||||
| print(f"{Colors.FAIL}❌ {error_msg}{Colors.ENDC}") | ||||||
|
|
||||||
|
Comment on lines
+144
to
+161
|
||||||
|
|
||||||
| TOKEN = _clean_env_kv(os.getenv("TOKEN"), "TOKEN") | ||||||
|
|
||||||
| # Default folder sources | ||||||
|
|
@@ -189,7 +209,6 @@ | |||||
| ) | ||||||
|
|
||||||
| _gh = httpx.Client(timeout=30) | ||||||
| MAX_RESPONSE_SIZE = 10 * 1024 * 1024 # 10 MB limit for external resources | ||||||
|
|
||||||
| # --------------------------------------------------------------------------- # | ||||||
| # 3. Helpers | ||||||
|
|
@@ -228,7 +247,7 @@ | |||||
| try: | ||||||
| # Resolve hostname to IPs (IPv4 and IPv6) | ||||||
| # We filter for AF_INET/AF_INET6 to ensure we get IP addresses | ||||||
| addr_info = socket.getaddrinfo(hostname, None, proto=socket.IPPROTO_TCP) | ||||||
Check warningCode scanning / Pylint (reported by Codacy) Missing function docstring Warning
Missing function docstring
Check warningCode scanning / Pylintpython3 (reported by Codacy) Missing function or method docstring Warning
Missing function or method docstring
|
||||||
| for res in addr_info: | ||||||
| # res is (family, type, proto, canonname, sockaddr) | ||||||
| # sockaddr is (address, port) for AF_INET/AF_INET6 | ||||||
|
|
@@ -247,12 +266,19 @@ | |||||
|
|
||||||
| return True | ||||||
|
|
||||||
| def validate_profile_id(profile_id: str) -> bool: | ||||||
| def is_valid_profile_id_format(profile_id: str) -> bool: | ||||||
|
||||||
| if not re.match(r"^[a-zA-Z0-9_-]+$", profile_id): | ||||||
| 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)") | ||||||
| return False | ||||||
| return True | ||||||
|
Comment on lines
+269
to
+274
|
||||||
|
|
||||||
| def validate_profile_id(profile_id: str) -> bool: | ||||||
| if not is_valid_profile_id_format(profile_id): | ||||||
| if not re.match(r"^[a-zA-Z0-9_-]+$", profile_id): | ||||||
| log.error("Invalid profile ID format (contains unsafe characters)") | ||||||
| elif len(profile_id) > 64: | ||||||
| log.error("Invalid profile ID length (max 64 chars)") | ||||||
| return False | ||||||
| return True | ||||||
|
Comment on lines
+276
to
283
|
||||||
|
|
||||||
|
|
@@ -376,9 +402,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 +880,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 | ||||||
|
|
||||||
| # --------------------------------------------------------------------------- # | ||||||
|
|
@@ -881,17 +907,31 @@ | |||||
| 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_profile_input(value: str) -> bool: | ||||||
Check warningCode scanning / Pylint (reported by Codacy) Missing function docstring Warning
Missing function docstring
|
||||||
| if not value: | ||||||
| return False | ||||||
| ids = [p.strip() for p in value.split(",") if p.strip()] | ||||||
| return bool(ids) and all(is_valid_profile_id_format(pid) for pid in ids) | ||||||
|
|
||||||
| p_input = get_validated_input( | ||||||
| f"{Colors.BOLD}Enter Control D Profile ID:{Colors.ENDC} ", | ||||||
| validate_profile_input, | ||||||
| "Invalid ID(s). Must be alphanumeric (including - and _), max 64 chars. Comma-separate for multiple." | ||||||
|
||||||
| ) | ||||||
| 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 | ||||||
|
|
||||||
| t_input = get_validated_input( | ||||||
| f"{Colors.BOLD}Enter Control D API Token:{Colors.ENDC} ", | ||||||
| lambda x: bool(x), | ||||||
|
||||||
| lambda x: bool(x), | |
| bool, |
Check failure
Code scanning / CodeQL
Clear-text logging of sensitive information High
sensitive data (password)
Check failure
Code scanning / CodeQL
Clear-text logging of sensitive information High
sensitive data (password)
Check failure
Code scanning / CodeQL
Clear-text logging of sensitive information High
Check warning
Code scanning / Pylint (reported by Codacy)
Wrong hanging indentation before block (add 4 spaces). Warning