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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [2.0.9] - 2026-02-21

### Changed
- HTTP URLs (`http://`) are now permitted; HTTPS is strongly recommended but no longer enforced. Use HTTP only on trusted local networks or for development.

## [2.0.8] - 2025-02-01

### Added
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ asyncio.run(main())

### TLS and self-signed certificates

Find My Device always requires HTTPS; plain HTTP is not allowed by this client. If you need to connect to a server with a self-signed certificate, you have two options:
HTTPS is strongly recommended for all connections to FMD server. HTTP is permitted for local development or trusted private networks, but should not be used in production. If you need to connect to a server with a self-signed certificate, you have two options:

- Preferred (secure): provide a custom SSLContext that trusts your CA or certificate
- Last resort (not for production): disable certificate validation explicitly
Expand All @@ -68,13 +68,13 @@ insecure_client = FmdClient("https://fmd.example.com", ssl=False)
```

Notes:
- HTTP (http://) is rejected. Use only HTTPS URLs.
- HTTPS is strongly recommended. Use HTTP only on trusted local networks or for development.
- Prefer a custom SSLContext over disabling verification.
- For higher security, consider pinning the server cert in your context.

> Warning
>
> Passing `ssl=False` disables TLS certificate validation and should only be used in development. For production, use a custom `ssl.SSLContext` that trusts your CA/certificate or pin the server certificate. The client enforces HTTPS and rejects `http://` URLs.
> Passing `ssl=False` disables TLS certificate validation and should only be used in development. For production, use a custom `ssl.SSLContext` that trusts your CA/certificate or pin the server certificate. Using `http://` URLs sends credentials and data in plaintext — only use HTTP on trusted local networks or for development purposes.

#### Pinning the exact server certificate (recommended for self-signed)

Expand Down
10 changes: 5 additions & 5 deletions fmd_api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ def __init__(
conn_limit_per_host: Optional[int] = None,
keepalive_timeout: Optional[float] = None,
) -> None:
# Enforce HTTPS only (FindMyDevice always uses TLS)
if base_url.lower().startswith("http://"):
raise ValueError("HTTPS is required for FmdClient base_url; plain HTTP is not allowed.")
# Validate that the URL uses a supported scheme
if not base_url.lower().startswith(("http://", "https://")):
raise ValueError("base_url must use http:// or https://")
self.base_url = base_url.rstrip("/")
self.session_duration = session_duration
self.cache_ttl = cache_ttl
Expand Down Expand Up @@ -127,7 +127,7 @@ async def create(
Factory method to create and authenticate an FmdClient.

Args:
base_url: HTTPS URL of the FMD server.
base_url: URL of the FMD server (https:// strongly recommended; http:// permitted for local/dev use).
fmd_id: User/device identifier.
password: Authentication password.
session_duration: Token validity in seconds (default 3600).
Expand All @@ -143,7 +143,7 @@ async def create(
Authenticated FmdClient instance.

Raises:
ValueError: If base_url uses HTTP instead of HTTPS.
ValueError: If base_url does not start with http:// or https://.
FmdApiException: If authentication fails or server returns an error.
asyncio.TimeoutError: If the request times out.
"""
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "fmd_api"
version = "2.0.8"
version = "2.0.9"
authors = [{name = "devinslick"}]
description = "A Python client for the FMD (Find My Device) server API"
readme = "README.md"
Expand Down
14 changes: 10 additions & 4 deletions tests/unit/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,16 @@ async def test_connector_configuration_applied():
await client.close()


def test_https_required():
"""FmdClient should reject non-HTTPS base URLs."""
with pytest.raises(ValueError, match="HTTPS is required"):
FmdClient("http://fmd.example.com")
def test_http_url_accepted():
"""FmdClient should accept plain HTTP base URLs (HTTPS is strongly recommended but not required)."""
client = FmdClient("http://fmd.example.com")
assert client.base_url == "http://fmd.example.com"


def test_invalid_scheme_rejected():
"""FmdClient should reject URLs with unsupported schemes."""
with pytest.raises(ValueError):
FmdClient("ftp://fmd.example.com")


@pytest.mark.asyncio
Expand Down
Loading