Added password_strength_checker.py in Python folder#128
Added password_strength_checker.py in Python folder#128x0lg0n merged 1 commit intox0lg0n:mainfrom Arni005:add-password-strength-checker
Conversation
Reviewer's GuideIntroduces a new standalone module that evaluates password strength by calculating entropy, detecting common patterns, applying granular scoring (length, character diversity, common-password penalties), and presenting feedback via both an interactive CLI and a programmatic quick-check API. Class diagram for PasswordStrengthChecker and related functionsclassDiagram
class PasswordStrengthChecker {
- common_passwords: set
- strength_levels: dict
+ __init__()
+ calculate_entropy(password: str) float
+ check_common_patterns(password: str) List[str]
+ check_password_strength(password: str) Tuple[str, str, List[str], int]
+ get_strength_breakdown(password: str)
}
class quick_check_mode {
+ quick_check_mode(password: str) dict
}
class interactive_mode {
+ interactive_mode()
}
class test_passwords {
+ test_passwords()
}
PasswordStrengthChecker <.. quick_check_mode : uses
PasswordStrengthChecker <.. interactive_mode : uses
PasswordStrengthChecker <.. test_passwords : uses
Flow diagram for password strength evaluation processflowchart TD
A["User inputs password"] --> B["Check password length"]
B --> C["Check character diversity"]
C --> D["Check if password is common"]
D --> E["Calculate entropy"]
E --> F["Check for common patterns"]
F --> G["Aggregate score and feedback"]
G --> H["Display feedback to user"]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
WalkthroughIntroduces a new Python module Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant PSC as PasswordStrengthChecker
participant Entropy as Entropy Engine
participant Patterns as Pattern Detector
participant Common as Common Passwords DB
User->>PSC: check_password_strength(password)
PSC->>PSC: Initialize score & feedback
PSC->>Common: Check if common password
Common-->>PSC: Penalty applied if match
PSC->>Entropy: calculate_entropy(password)
Entropy-->>PSC: Entropy score
PSC->>Patterns: check_common_patterns(password)
Patterns-->>PSC: Pattern issues detected
PSC->>PSC: Evaluate length & character variety
PSC->>PSC: Build categorized feedback
PSC->>PSC: Map score to strength level
PSC-->>User: (strength, emoji, feedback, score)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR Compliance Guide 🔍Below is a summary of compliance checks for this PR:
Compliance status legend🟢 - Fully Compliant🟡 - Partial Compliant 🔴 - Not Compliant ⚪ - Requires Further Human Verification 🏷️ - Compliance label |
||||||||||||||||||||||||
There was a problem hiding this comment.
Hey there - I've reviewed your changes - here's some feedback:
- Consider decoupling the CLI/print logic from the strength‐checking class so you can reuse the core analysis in different contexts without relying on stdout.
- The repeated‐substring detection is O(n³) in the worst case; you might limit the substring length or optimize it to avoid performance issues on very long passwords.
- Hard-coding the common passwords set prevents easy updates—consider loading that list from a configurable external source so it stays maintainable and updatable.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider decoupling the CLI/print logic from the strength‐checking class so you can reuse the core analysis in different contexts without relying on stdout.
- The repeated‐substring detection is O(n³) in the worst case; you might limit the substring length or optimize it to avoid performance issues on very long passwords.
- Hard-coding the common passwords set prevents easy updates—consider loading that list from a configurable external source so it stays maintainable and updatable.
## Individual Comments
### Comment 1
<location> `Python/password_strength_checker.py:164-167` </location>
<code_context>
+ for item in warnings[:3]: # Show top 3 suggestions
+ print(f" {item}")
+
+ if positive and not critical: # Only show positives if no critical issues
+ print("\nStrengths:")
+ for item in positive[:3]:
</code_context>
<issue_to_address>
**suggestion:** Positive feedback is suppressed when critical issues exist.
Consider displaying strengths alongside critical issues to provide more balanced feedback.
```suggestion
if positive:
print("\nStrengths:")
for item in positive[:3]:
print(f" {item}")
```
</issue_to_address>
### Comment 2
<location> `Python/password_strength_checker.py:77` </location>
<code_context>
+
+ return patterns
+
+ def check_password_strength(self, password: str) -> Tuple[str, str, List[str], int]:
+ """Comprehensive password strength analysis"""
+ score = 0
</code_context>
<issue_to_address>
**issue (complexity):** Consider refactoring the password strength checker by splitting large methods into smaller private scorers and moving I/O logic out of the class.
Here are a few focused refactorings that will keep all behavior the same but dramatically reduce nesting and size:
1. Split your one big `check_password_strength` into small private scorers:
```python
class PasswordStrengthChecker:
def __init__(…): …
def _score_length(self, pwd: str) -> Tuple[int, List[str]]:
score, fb = 0, []
if len(pwd) >= 16:
score += 2; fb.append("✅ Excellent password length")
elif len(pwd) >= 12:
score += 1; fb.append("✅ Good password length")
elif len(pwd) >= 8:
score += 1; fb.append("⚠️ Consider using 12+ characters")
else:
fb.append("❌ Password should be at least 8 characters long")
return score, fb
def _score_diversity(self, pwd: str) -> Tuple[int, List[str]]:
rules = [
(r'[A-Z]', "uppercase letters"),
(r'[a-z]', "lowercase letters"),
(r'\d', "numbers"),
(r'[^A-Za-z0-9]', "special characters"),
]
score, fb = 0, []
for patt, name in rules:
if re.search(patt, pwd):
score += 1; fb.append(f"✅ Contains {name}")
else:
fb.append(f"❌ Include {name}")
return score, fb
def _apply_common_password_penalty(self, pwd: str) -> Tuple[int, List[str]]:
norm = re.sub(r'[^a-z0-9]', '', pwd.lower())
if pwd.lower() in self.common_passwords or norm in self.common_passwords:
return -2, ["❌ This is a commonly used password"]
return 0, []
def _score_entropy(self, pwd: str) -> Tuple[int, List[str]]:
ent = self.calculate_entropy(pwd)
if ent >= 60:
return 1, ["✅ High entropy"]
if ent >= 40:
return 0, ["⚠️ Moderate entropy"]
return 0, ["❌ Low entropy"]
```
Then your `check_password_strength` becomes:
```python
def check_password_strength(self, pwd: str):
total, feedback = 0, []
for scorer in (self._score_length,
self._score_diversity,
self._apply_common_password_penalty,
self._score_entropy):
sc, fb = scorer(pwd)
total += sc
feedback.extend(fb)
patterns = self.check_common_patterns(pwd)
if patterns:
total = max(0, total - 1)
feedback.extend(patterns)
total = min(max(total, 0), 5)
strength, emoji = self.strength_levels[total]
return strength, emoji, feedback, total
```
2. Simplify `check_common_patterns` with any() and comprehensions:
```python
def check_common_patterns(self, pwd: str) -> List[str]:
lower = pwd.lower()
out = []
if re.search(r'(.)\1{2,}', pwd):
out.append("Avoid repeated characters")
seqs = ['abc','123','qwe','asd','zxc']
if any(s in lower or s[::-1] in lower for s in seqs):
out.append("Avoid sequential patterns")
# repeated substrings of length ≥3
if any(pwd.count(sub) > 1
for length in range(3, len(pwd)//2+1)
for sub in {pwd[i:i+length] for i in range(len(pwd)-length+1)}):
out.append("Avoid repeated substrings")
return out
```
3. Pull all `print` and `getpass` logic out of this class into a separate `password_cli.py` (or similar). Your checker stays pure-logic and returns data; the CLI module handles I/O.
</issue_to_address>
### Comment 3
<location> `Python/password_strength_checker.py:222-225` </location>
<code_context>
</code_context>
<issue_to_address>
**issue (code-quality):** Avoid loops in tests. ([`no-loop-in-tests`](https://docs.sourcery.ai/Reference/Rules-and-In-Line-Suggestions/Python/Default-Rules/no-loop-in-tests))
<details><summary>Explanation</summary>Avoid complex code, like loops, in test functions.
Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:
* loops
* conditionals
Some ways to fix this:
* Use parametrized tests to get rid of the loop.
* Move the complex logic into helpers.
* Move the complex part into pytest fixtures.
> Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.
Software Engineering at Google / [Don't Put Logic in Tests](https://abseil.io/resources/swe-book/html/ch12.html#donapostrophet_put_logic_in_tests)
</details>
</issue_to_address>
### Comment 4
<location> `Python/password_strength_checker.py:28` </location>
<code_context>
def calculate_entropy(self, password: str) -> float:
"""Calculate password entropy to measure randomness"""
if not password:
return 0
# Character pool size estimation
char_pool = 0
if re.search(r'[a-z]', password):
char_pool += 26
if re.search(r'[A-Z]', password):
char_pool += 26
if re.search(r'[0-9]', password):
char_pool += 10
if re.search(r'[^A-Za-z0-9]', password):
char_pool += 32 # Common special characters
if char_pool == 0:
return 0
# Entropy formula: log2(pool_size^length)
entropy = len(password) * math.log2(char_pool)
return entropy
</code_context>
<issue_to_address>
**issue (code-quality):** We've found these issues:
- Inline variable that is immediately returned ([`inline-immediately-returned-variable`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/inline-immediately-returned-variable/))
- Lift code into else after jump in control flow ([`reintroduce-else`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/reintroduce-else/))
- Replace if statement with if expression ([`assign-if-exp`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/assign-if-exp/))
</issue_to_address>
### Comment 5
<location> `Python/password_strength_checker.py:128` </location>
<code_context>
def check_password_strength(self, password: str) -> Tuple[str, str, List[str], int]:
"""Comprehensive password strength analysis"""
score = 0
feedback = []
# Length scoring (more granular)
if len(password) >= 16:
score += 2
feedback.append("✅ Excellent password length")
elif len(password) >= 12:
score += 1
feedback.append("✅ Good password length")
elif len(password) >= 8:
score += 1
feedback.append("⚠️ Consider using 12+ characters for better security")
else:
feedback.append("❌ Password should be at least 8 characters long")
# Character diversity scoring
checks = {
'uppercase': (r'[A-Z]', "Include uppercase letters"),
'lowercase': (r'[a-z]', "Include lowercase letters"),
'numbers': (r'[0-9]', "Include numbers"),
'special': (r'[^A-Za-z0-9]', "Include special characters")
}
for check_type, (pattern, message) in checks.items():
if re.search(pattern, password):
score += 1
feedback.append(f"✅ Contains {check_type} characters")
else:
feedback.append(f"❌ {message}")
# Common password check (improved)
normalized_password = re.sub(r'[^a-z0-9]', '', password.lower())
if (password.lower() in self.common_passwords or
normalized_password in self.common_passwords):
score = max(0, score - 2) # Heavy penalty for common passwords
feedback.append("❌ This is a commonly used password - choose something more unique")
# Entropy check
entropy = self.calculate_entropy(password)
if entropy >= 60:
score += 1
feedback.append("✅ High entropy - excellent randomness")
elif entropy >= 40:
feedback.append("⚠️ Moderate entropy - consider more random characters")
else:
feedback.append("❌ Low entropy - password is too predictable")
# Pattern checks
pattern_feedback = self.check_common_patterns(password)
if pattern_feedback:
score = max(0, score - 1)
feedback.extend(pattern_feedback)
# Cap score at maximum
score = min(score, 5)
# Get strength rating
strength, emoji = self.strength_levels.get(score, ("Very Weak", "🔴"))
return strength, emoji, feedback, score
</code_context>
<issue_to_address>
**issue (code-quality):** Use named expression to simplify assignment and conditional ([`use-named-expression`](https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-named-expression/))
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| if positive and not critical: # Only show positives if no critical issues | ||
| print("\nStrengths:") | ||
| for item in positive[:3]: | ||
| print(f" {item}") |
There was a problem hiding this comment.
suggestion: Positive feedback is suppressed when critical issues exist.
Consider displaying strengths alongside critical issues to provide more balanced feedback.
| if positive and not critical: # Only show positives if no critical issues | |
| print("\nStrengths:") | |
| for item in positive[:3]: | |
| print(f" {item}") | |
| if positive: | |
| print("\nStrengths:") | |
| for item in positive[:3]: | |
| print(f" {item}") |
|
|
||
| return patterns | ||
|
|
||
| def check_password_strength(self, password: str) -> Tuple[str, str, List[str], int]: |
There was a problem hiding this comment.
issue (complexity): Consider refactoring the password strength checker by splitting large methods into smaller private scorers and moving I/O logic out of the class.
Here are a few focused refactorings that will keep all behavior the same but dramatically reduce nesting and size:
- Split your one big
check_password_strengthinto small private scorers:
class PasswordStrengthChecker:
def __init__(…): …
def _score_length(self, pwd: str) -> Tuple[int, List[str]]:
score, fb = 0, []
if len(pwd) >= 16:
score += 2; fb.append("✅ Excellent password length")
elif len(pwd) >= 12:
score += 1; fb.append("✅ Good password length")
elif len(pwd) >= 8:
score += 1; fb.append("⚠️ Consider using 12+ characters")
else:
fb.append("❌ Password should be at least 8 characters long")
return score, fb
def _score_diversity(self, pwd: str) -> Tuple[int, List[str]]:
rules = [
(r'[A-Z]', "uppercase letters"),
(r'[a-z]', "lowercase letters"),
(r'\d', "numbers"),
(r'[^A-Za-z0-9]', "special characters"),
]
score, fb = 0, []
for patt, name in rules:
if re.search(patt, pwd):
score += 1; fb.append(f"✅ Contains {name}")
else:
fb.append(f"❌ Include {name}")
return score, fb
def _apply_common_password_penalty(self, pwd: str) -> Tuple[int, List[str]]:
norm = re.sub(r'[^a-z0-9]', '', pwd.lower())
if pwd.lower() in self.common_passwords or norm in self.common_passwords:
return -2, ["❌ This is a commonly used password"]
return 0, []
def _score_entropy(self, pwd: str) -> Tuple[int, List[str]]:
ent = self.calculate_entropy(pwd)
if ent >= 60:
return 1, ["✅ High entropy"]
if ent >= 40:
return 0, ["⚠️ Moderate entropy"]
return 0, ["❌ Low entropy"]Then your check_password_strength becomes:
def check_password_strength(self, pwd: str):
total, feedback = 0, []
for scorer in (self._score_length,
self._score_diversity,
self._apply_common_password_penalty,
self._score_entropy):
sc, fb = scorer(pwd)
total += sc
feedback.extend(fb)
patterns = self.check_common_patterns(pwd)
if patterns:
total = max(0, total - 1)
feedback.extend(patterns)
total = min(max(total, 0), 5)
strength, emoji = self.strength_levels[total]
return strength, emoji, feedback, total- Simplify
check_common_patternswith any() and comprehensions:
def check_common_patterns(self, pwd: str) -> List[str]:
lower = pwd.lower()
out = []
if re.search(r'(.)\1{2,}', pwd):
out.append("Avoid repeated characters")
seqs = ['abc','123','qwe','asd','zxc']
if any(s in lower or s[::-1] in lower for s in seqs):
out.append("Avoid sequential patterns")
# repeated substrings of length ≥3
if any(pwd.count(sub) > 1
for length in range(3, len(pwd)//2+1)
for sub in {pwd[i:i+length] for i in range(len(pwd)-length+1)}):
out.append("Avoid repeated substrings")
return out- Pull all
printandgetpasslogic out of this class into a separatepassword_cli.py(or similar). Your checker stays pure-logic and returns data; the CLI module handles I/O.
| for pwd in test_cases: | ||
| result = quick_check_mode(pwd) | ||
| print(f"\nPassword: {pwd}") | ||
| print(f"Strength: {result['strength']} {result['emoji']} ({result['score']}/5)") |
There was a problem hiding this comment.
issue (code-quality): Avoid loops in tests. (no-loop-in-tests)
Explanation
Avoid complex code, like loops, in test functions.Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:
- loops
- conditionals
Some ways to fix this:
- Use parametrized tests to get rid of the loop.
- Move the complex logic into helpers.
- Move the complex part into pytest fixtures.
Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.
Software Engineering at Google / Don't Put Logic in Tests
PR Code Suggestions ✨Explore these optional code suggestions:
|
|||||||||||||||||
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
Python/password_strength_checker.py (3)
9-15: Consider expanding the common passwords list.The current list contains ~25 common passwords, which provides basic protection. For production use, consider loading from a more comprehensive list (e.g., top 10,000 common passwords) either from a file or a third-party library like
zxcvbn.
67-73: Consider optimizing substring detection for very long passwords.The nested loops combined with
password.count()result in O(n³) complexity. For typical passwords (<50 characters), this is fine. However, if the tool might process very long inputs, consider using a more efficient algorithm (e.g., suffix arrays or rolling hash).
1-232: Optional enhancements for future consideration.The implementation is solid overall. A few optional enhancements to consider:
- Unicode support: The entropy calculation doesn't account for Unicode/non-ASCII characters, which might be used in international passwords.
- Configurable thresholds: Hardcoded values (entropy levels at lines 119-125, score cap at line 134) could be made configurable via constructor parameters for different security contexts.
- Common passwords loading: Consider supporting loading common passwords from an external file to allow users to maintain their own lists without modifying code.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
Python/password_strength_checker.py(1 hunks)
🧰 Additional context used
🪛 Ruff (0.14.1)
Python/password_strength_checker.py
217-217: Local variable checker is assigned to but never used
Remove assignment to unused variable checker
(F841)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Sourcery review
🔇 Additional comments (7)
Python/password_strength_checker.py (7)
1-4: LGTM!The imports are appropriate for the functionality. Good choice using
getpassfor secure password input in interactive mode.
27-48: Entropy calculation is a reasonable approximation.The implementation correctly calculates theoretical entropy based on character pool size. Note that this assumes uniform random selection, so passwords like "Password123!" will have the same calculated entropy as truly random strings of the same length and character types. This is acceptable for a password strength meter, as true entropy calculation would require analyzing character frequency distributions.
77-139: Well-structured password strength analysis.The scoring logic is comprehensive and balanced:
- Granular length scoring encourages longer passwords
- Character diversity checks cover all major types
- Smart normalization before common password check (removes special chars)
- Entropy-based scoring adds depth beyond simple rules
- Pattern penalties help catch weak passwords that pass basic checks
141-167: Good user experience design.The feedback prioritization (critical → warnings → strengths) and limiting to top 3 items per category provides clear, actionable guidance without overwhelming the user.
169-191: LGTM!The interactive mode uses
getpass.getpass()for secure password input and handles edge cases appropriately. The flow is user-friendly with clear prompts and retry options.
193-203: LGTM!Clean programmatic API that returns structured data. Good separation from the interactive mode.
227-232: LGTM!Standard entry point that runs interactive mode by default, which is appropriate for an end-user tool. The commented test option provides an easy way to view examples.
| "MyV3ryS3cur3P@ssw0rd2024!" # Strong | ||
| ] | ||
|
|
||
| checker = PasswordStrengthChecker() |
There was a problem hiding this comment.
Remove unused variable.
The checker variable is assigned but never used. Line 223 calls quick_check_mode(pwd) which creates its own checker instance internally.
Apply this diff to remove the unused variable:
- checker = PasswordStrengthChecker()
-
print("Testing Password Examples:")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| checker = PasswordStrengthChecker() | |
| print("Testing Password Examples:") |
🧰 Tools
🪛 Ruff (0.14.1)
217-217: Local variable checker is assigned to but never used
Remove assignment to unused variable checker
(F841)
🤖 Prompt for AI Agents
In Python/password_strength_checker.py around line 217, the variable `checker`
is created but never used (quick_check_mode(pwd) makes its own checker); remove
the unused assignment `checker = PasswordStrengthChecker()` from that location
so there is no dead variable left in the code and ensure surrounding code still
calls quick_check_mode(pwd) as before.
Created Password Strength Checker with smarter scoring, clearer feedback, and an interactive user experience.
Summary by Sourcery
Introduce a comprehensive Python password strength checker script featuring advanced scoring based on entropy, character diversity, and common pattern detection, along with interactive and programmatic usage modes.
New Features:
Summary by CodeRabbit
New Features