Skip to content

Commit 2e205dc

Browse files
authored
Merge pull request #54 from michalpokusa/4.0.0-examples-refactor-authentication-mimetypes
4.0.0 - Huge refactor, more examples, authentication, configurable MIMETypes
2 parents 8263b8a + f59d10c commit 2e205dc

33 files changed

+1766
-700
lines changed

README.rst

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ HTTP Server for CircuitPython.
3030
- Gives access to request headers, query parameters, body and client's address, the one from which the request came.
3131
- Supports chunked transfer encoding.
3232
- Supports URL parameters and wildcard URLs.
33+
- Supports HTTP Basic and Bearer Authentication on both server and route per level.
3334

3435

3536
Dependencies

adafruit_httpserver/__init__.py

+60-2
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
`adafruit_httpserver`
66
================================================================================
77
8-
Simple HTTP Server for CircuitPython
8+
Socket based HTTP Server for CircuitPython
99
1010
11-
* Author(s): Dan Halbert
11+
* Author(s): Dan Halbert, Michał Pokusa
1212
1313
Implementation Notes
1414
--------------------
@@ -21,3 +21,61 @@
2121

2222
__version__ = "0.0.0+auto.0"
2323
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_HTTPServer.git"
24+
25+
26+
from .authentication import (
27+
Basic,
28+
Bearer,
29+
check_authentication,
30+
require_authentication,
31+
)
32+
from .exceptions import (
33+
ServerStoppedError,
34+
AuthenticationError,
35+
InvalidPathError,
36+
ParentDirectoryReferenceError,
37+
BackslashInPathError,
38+
ServingFilesDisabledError,
39+
FileNotExistsError,
40+
)
41+
from .headers import Headers
42+
from .methods import (
43+
GET,
44+
POST,
45+
PUT,
46+
DELETE,
47+
PATCH,
48+
HEAD,
49+
OPTIONS,
50+
TRACE,
51+
CONNECT,
52+
)
53+
from .mime_types import MIMETypes
54+
from .request import Request
55+
from .response import (
56+
Response,
57+
FileResponse,
58+
ChunkedResponse,
59+
JSONResponse,
60+
Redirect,
61+
)
62+
from .server import Server
63+
from .status import (
64+
Status,
65+
OK_200,
66+
CREATED_201,
67+
ACCEPTED_202,
68+
NO_CONTENT_204,
69+
PARTIAL_CONTENT_206,
70+
TEMPORARY_REDIRECT_307,
71+
PERMANENT_REDIRECT_308,
72+
BAD_REQUEST_400,
73+
UNAUTHORIZED_401,
74+
FORBIDDEN_403,
75+
NOT_FOUND_404,
76+
METHOD_NOT_ALLOWED_405,
77+
TOO_MANY_REQUESTS_429,
78+
INTERNAL_SERVER_ERROR_500,
79+
NOT_IMPLEMENTED_501,
80+
SERVICE_UNAVAILABLE_503,
81+
)

adafruit_httpserver/authentication.py

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# SPDX-FileCopyrightText: Copyright (c) 2022 Dan Halbert for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
"""
5+
`adafruit_httpserver.authentication`
6+
====================================================
7+
* Author(s): Michał Pokusa
8+
"""
9+
10+
try:
11+
from typing import Union, List
12+
except ImportError:
13+
pass
14+
15+
from binascii import b2a_base64
16+
17+
from .exceptions import AuthenticationError
18+
from .request import Request
19+
20+
21+
class Basic:
22+
"""Represents HTTP Basic Authentication."""
23+
24+
def __init__(self, username: str, password: str) -> None:
25+
self._value = b2a_base64(f"{username}:{password}".encode()).decode().strip()
26+
27+
def __str__(self) -> str:
28+
return f"Basic {self._value}"
29+
30+
31+
class Bearer:
32+
"""Represents HTTP Bearer Token Authentication."""
33+
34+
def __init__(self, token: str) -> None:
35+
self._value = token
36+
37+
def __str__(self) -> str:
38+
return f"Bearer {self._value}"
39+
40+
41+
def check_authentication(request: Request, auths: List[Union[Basic, Bearer]]) -> bool:
42+
"""
43+
Returns ``True`` if request is authorized by any of the authentications, ``False`` otherwise.
44+
"""
45+
46+
auth_header = request.headers.get("Authorization")
47+
48+
if auth_header is None:
49+
return False
50+
51+
return any(auth_header == str(auth) for auth in auths)
52+
53+
54+
def require_authentication(request: Request, auths: List[Union[Basic, Bearer]]) -> None:
55+
"""
56+
Checks if the request is authorized and raises ``AuthenticationError`` if not.
57+
58+
If the error is not caught, the server will return ``401 Unauthorized``.
59+
"""
60+
61+
if not check_authentication(request, auths):
62+
raise AuthenticationError(
63+
"Request is not authenticated by any of the provided authentications"
64+
)

adafruit_httpserver/exceptions.py

+14-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,18 @@
88
"""
99

1010

11+
class ServerStoppedError(Exception):
12+
"""
13+
Raised when ``.poll`` is called on a stopped ``Server``.
14+
"""
15+
16+
17+
class AuthenticationError(Exception):
18+
"""
19+
Raised by ``require_authentication`` when the ``Request`` is not authorized.
20+
"""
21+
22+
1123
class InvalidPathError(Exception):
1224
"""
1325
Parent class for all path related errors.
@@ -34,9 +46,9 @@ def __init__(self, path: str) -> None:
3446
super().__init__(f"Backslash in path: {path}")
3547

3648

37-
class ResponseAlreadySentError(Exception):
49+
class ServingFilesDisabledError(Exception):
3850
"""
39-
Another ``HTTPResponse`` has already been sent. There can only be one per ``HTTPRequest``.
51+
Raised when ``root_path`` is not set and there is no handler for ``request``.
4052
"""
4153

4254

adafruit_httpserver/headers.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#
33
# SPDX-License-Identifier: MIT
44
"""
5-
`adafruit_httpserver.headers.HTTPHeaders`
5+
`adafruit_httpserver.headers`
66
====================================================
77
* Author(s): Michał Pokusa
88
"""
@@ -13,7 +13,7 @@
1313
pass
1414

1515

16-
class HTTPHeaders:
16+
class Headers:
1717
"""
1818
A dict-like class for storing HTTP headers.
1919
@@ -23,7 +23,7 @@ class HTTPHeaders:
2323
2424
Examples::
2525
26-
headers = HTTPHeaders({"Content-Type": "text/html", "Content-Length": "1024"})
26+
headers = Headers({"Content-Type": "text/html", "Content-Length": "1024"})
2727
2828
len(headers)
2929
# 2
@@ -80,7 +80,7 @@ def update(self, headers: Dict[str, str]):
8080

8181
def copy(self):
8282
"""Returns a copy of the headers."""
83-
return HTTPHeaders(dict(self._storage.values()))
83+
return Headers(dict(self._storage.values()))
8484

8585
def __getitem__(self, name: str):
8686
return self._storage[name.lower()][1]

adafruit_httpserver/methods.py

+10-22
Original file line numberDiff line numberDiff line change
@@ -2,38 +2,26 @@
22
#
33
# SPDX-License-Identifier: MIT
44
"""
5-
`adafruit_httpserver.methods.HTTPMethod`
5+
`adafruit_httpserver.methods`
66
====================================================
77
* Author(s): Michał Pokusa
88
"""
99

1010

11-
class HTTPMethod: # pylint: disable=too-few-public-methods
12-
"""Enum with HTTP methods."""
11+
GET = "GET"
1312

14-
GET = "GET"
15-
"""GET method."""
13+
POST = "POST"
1614

17-
POST = "POST"
18-
"""POST method."""
15+
PUT = "PUT"
1916

20-
PUT = "PUT"
21-
"""PUT method"""
17+
DELETE = "DELETE"
2218

23-
DELETE = "DELETE"
24-
"""DELETE method"""
19+
PATCH = "PATCH"
2520

26-
PATCH = "PATCH"
27-
"""PATCH method"""
21+
HEAD = "HEAD"
2822

29-
HEAD = "HEAD"
30-
"""HEAD method"""
23+
OPTIONS = "OPTIONS"
3124

32-
OPTIONS = "OPTIONS"
33-
"""OPTIONS method"""
25+
TRACE = "TRACE"
3426

35-
TRACE = "TRACE"
36-
"""TRACE method"""
37-
38-
CONNECT = "CONNECT"
39-
"""CONNECT method"""
27+
CONNECT = "CONNECT"

adafruit_httpserver/mime_type.py

-100
This file was deleted.

0 commit comments

Comments
 (0)