Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@
"bmewburn.vscode-intelephense-client",
"xdebug.php-debug",
"ms-python.vscode-pylance",
"pamaron.pytest-runner",
"coderabbit.coderabbit-vscode",
"ms-python.black-formatter",
"jeff-hykin.better-dockerfile-syntax",
Expand Down
13 changes: 13 additions & 0 deletions docs/DEBUG_API_SERVER.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ All application settings can also be initialized via the `APP_CONF_OVERRIDE` doc

There are several ways to check if the GraphQL server is running.

## Flask debug mode (environment)

You can control whether the Flask development debugger is enabled by setting the environment variable `FLASK_DEBUG` (default: `False`). Enabling debug mode will turn on the interactive debugger which may expose a remote code execution (RCE) vector if the server is reachable; **only enable this for local development** and never in production. Valid truthy values are: `1`, `true`, `yes`, `on` (case-insensitive).

In the running container you can set this variable via Docker Compose or your environment, for example:

```yaml
environment:
- FLASK_DEBUG=1
```

When enabled, the GraphQL server startup logs will indicate the debug setting.

### Init Check

You can navigate to System Info -> Init Check to see if `isGraphQLServerRunning` is ticked:
Expand Down
56 changes: 40 additions & 16 deletions front/plugins/plugin_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,22 @@ def is_typical_router_ip(ip_address):
# -------------------------------------------------------------------
# Check if a valid MAC address
def is_mac(input):
input_str = str(input).lower() # Convert to string and lowercase so non-string values won't raise errors
input_str = str(input).lower().strip() # Convert to string and lowercase so non-string values won't raise errors

isMac = bool(re.match("[0-9a-f]{2}([-:]?)[0-9a-f]{2}(\\1[0-9a-f]{2}){4}$", input_str))
# Full MAC (6 octets) e.g. AA:BB:CC:DD:EE:FF
full_mac_re = re.compile(r"^[0-9a-f]{2}([-:]?)[0-9a-f]{2}(\1[0-9a-f]{2}){4}$")

if not isMac: # If it's not a MAC address, log the input
mylog('verbose', [f'[is_mac] not a MAC: {input_str}'])
# Wildcard prefix format: exactly 3 octets followed by a trailing '*' component
# Examples: AA:BB:CC:*
wildcard_re = re.compile(r"^[0-9a-f]{2}[-:]?[0-9a-f]{2}[-:]?[0-9a-f]{2}[-:]?\*$")

return isMac
if full_mac_re.match(input_str) or wildcard_re.match(input_str):
return True

# If it's not a MAC address or allowed wildcard pattern, log the input
mylog('verbose', [f'[is_mac] not a MAC: {input_str}'])

return False
Comment thread
adamoutler marked this conversation as resolved.


# -------------------------------------------------------------------
Expand Down Expand Up @@ -168,20 +176,36 @@ def decode_settings_base64(encoded_str, convert_types=True):

# -------------------------------------------------------------------
def normalize_mac(mac):
# Split the MAC address by colon (:) or hyphen (-) and convert each part to uppercase
parts = mac.upper().split(':')

# If the MAC address is split by hyphen instead of colon
if len(parts) == 1:
parts = mac.upper().split('-')
"""
Normalize a MAC address to the standard format with colon separators.
For example, "aa-bb-cc-dd-ee-ff" will be normalized to "AA:BB:CC:DD:EE:FF".
Wildcard MAC addresses like "AA:BB:CC:*" will be normalized to "AA:BB:CC:*".

# Normalize each part to have exactly two hexadecimal digits
normalized_parts = [part.zfill(2) for part in parts]
:param mac: The MAC address to normalize.
:return: The normalized MAC address.
"""
s = str(mac).upper().strip()

# Join the parts with colon (:)
normalized_mac = ':'.join(normalized_parts)
# Determine separator if present, prefer colon, then hyphen
if ':' in s:
parts = s.split(':')
elif '-' in s:
parts = s.split('-')
else:
# No explicit separator; attempt to split every two chars
parts = [s[i:i + 2] for i in range(0, len(s), 2)]

normalized_parts = []
for part in parts:
part = part.strip()
if part == '*':
normalized_parts.append('*')
else:
# Ensure two hex digits (zfill is fine for alphanumeric input)
normalized_parts.append(part.zfill(2))

return normalized_mac
# Use colon as canonical separator
return ':'.join(normalized_parts)


# -------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ httplib2
gunicorn
git+https://github.com/foreign-sub/aiofreepybox.git
mcp
pydantic>=2.0,<3.0
2 changes: 1 addition & 1 deletion scripts/generate-device-inventory.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ def build_row(


def generate_rows(args: argparse.Namespace, header: list[str]) -> list[dict[str, str]]:
now = dt.datetime.utcnow()
now = dt.datetime.now(dt.timezone.utc)
macs: set[str] = set()
ip_pool = prepare_ip_pool(args.network)

Expand Down
Empty file added server/api_server/__init__.py
Empty file.
Loading