diff --git a/CHANGELOG.md b/CHANGELOG.md index d559ebf..7b6d69c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index 3f70132..8e5f253 100644 --- a/README.md +++ b/README.md @@ -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 @@ -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) diff --git a/fmd_api/client.py b/fmd_api/client.py index daa1e72..e0b718e 100644 --- a/fmd_api/client.py +++ b/fmd_api/client.py @@ -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 @@ -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). @@ -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. """ diff --git a/pyproject.toml b/pyproject.toml index 2e1b81c..6cbbd74 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 360557e..4745c05 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -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