• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

esnet-security / SCRAM / 20344324773

18 Dec 2025 04:46PM UTC coverage: 93.715% (-0.7%) from 94.409%
20344324773

Pull #188

github

web-flow
Merge 63b6cf916 into ba7ca24e4
Pull Request #188: feat(client): A few improvements to clients

66 of 72 branches covered (91.67%)

Branch coverage included in aggregate %.

34 of 44 new or added lines in 5 files covered. (77.27%)

4 existing lines in 2 files now uncovered.

1291 of 1376 relevant lines covered (93.82%)

0.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.57
/scram/shared/shared_code.py
1
"""A Module for defining shared code."""
2

3
import secrets
1✔
4
import string
1✔
5

6

7
def make_random_password(length: int = 20, min_digits: int = 5, max_attempts: int = 10000) -> str:
1✔
8
    """make_random_password replaces the deprecated django make_random_password function.
9

10
    Generates a random password of a specified length containing at least a specified number of digits using the
11
    official python best practices and some additional sanity checks. The python docs for this can be found at
12
    https://docs.python.org/3/library/secrets.html#recipes-and-best-practices. Note that generating long passwords
13
    with a high number of digits (>100) is inefficient and should be avoided. This password should only be used for
14
    temporary purposes, such as for a user to log in to the web interface and change their password.
15

16
    Args:
17
        length (int, optional): The total length of the password to generate. Defaults to 20.
18
        min_digits (int, optional): The minimum number of digits the password needs. Defaults to 5.
19
        max_attempts (int, optional): The maximum number of attempts to generate a valid password. Defaults to 10000.
20

21
    Raises:
22
        ValueError: Password length must be at least 1
23
        ValueError: min_digits cannot be negative
24
        ValueError: min_digits cannot exceed password length
25
        ValueError: For performance reasons, min_digits cannot exceed 30% of the password length
26
        RuntimeError: Failed to generate a valid password after max_attempts attempts
27

28
    Returns:
29
        password (str): The generated password.
30
    """
31
    if length < 1:
1✔
32
        message = "Password length must be at least 1"
×
33
        raise ValueError(message)
×
34
    if min_digits < 0:
1✔
35
        message = "min_digits cannot be negative"
×
36
        raise ValueError(message)
×
37
    if min_digits > length:
1✔
38
        message = "min_digits cannot exceed password length"
×
39
        raise ValueError(message)
×
40
    # Only allow a somewhat arbitrary threshold of 30% of the password length for min_digits, for performance reasons.
41
    if min_digits > length * 0.3:
1✔
42
        message = "For performance reasons, min_digits cannot exceed 30% of the password length"
×
43
        raise ValueError(message)
×
44

45
    alphabet = string.ascii_letters + string.digits
1✔
46

47
    for _attempt in range(max_attempts):
1✔
48
        password = "".join(secrets.choice(alphabet) for _i in range(length))
1✔
49
        if (
1✔
50
            any(c.islower() for c in password)
51
            and any(c.isupper() for c in password)
52
            and sum(c.isdigit() for c in password) >= min_digits
53
        ):
54
            return password
1✔
55

56
    # If we reached this point, we failed to generate a valid password after max_attempts attempts, likely due to the
57
    # required password length being very long and the min_digits being high.
58
    message = f"Failed to generate a valid password after {max_attempts} attempts"
×
59
    raise RuntimeError(message)
×
60

61

62
def get_client_ip(request) -> str | None:
1✔
63
    """Get the client's IP address from the request.
64

65
    Checks the HTTP_X_FORWARDED_FOR header first, then falls back to REMOTE_ADDR.
66

67
    Returns:
68
        IP address from the client
69
    """
70
    x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
1✔
71
    if x_forwarded_for:
1✔
NEW
72
        ip = x_forwarded_for.split(",")[0]
×
73
    else:
74
        ip = request.META.get("REMOTE_ADDR")
1✔
75
    return ip
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc