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
86 changes: 26 additions & 60 deletions .github/workflows/docs-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,68 +4,34 @@ on:
push:
branches:
- master
- documentation # @todo Remove this before merging PR.

jobs:
docs:
runs-on: ubuntu-latest
steps:
- name: Checks out repo
uses: actions/checkout@v2

- name: Generates HTML documentation
uses: synchronizing/sphinx-action@master
with:
docs-folder: "docs/"
pre-build-command: "apt-get update -y && apt-get install -y build-essential && make docs-install"

- name: Builds the PDF documentation
uses: synchronizing/sphinx-action@master
with:
pre-build-command: "apt-get update -y && apt-get install -y build-essential latexmk texlive-latex-recommended texlive-latex-extra texlive-fonts-recommended && make docs-install"
build-command: "make latexpdf"
docs-folder: "docs/"

- name: Saves the HTML build documentation
uses: actions/upload-artifact@v4
with:
path: docs/build/html/

- name: Saves the PDF build documentation
uses: actions/upload-artifact@v4
with:
path: docs/build/latex/aioauth.pdf

- name: Commits docs changes to gh-pages branch
run: |

# Copies documentation outside of git folder.
mkdir -p ../docs/html ../docs/pdf
cp -r docs/build/html ../docs/
cp docs/build/latex/aioauth.pdf ../docs/pdf/

# Removes all of the content of the current folder.
sudo rm -rf *

# Checks out to gh-pages branch.
git checkout -b gh-pages

# Copies files to branch.
cp -r ../docs/html/* .
cp ../docs/pdf/aioauth.pdf .

# Sets up no Jekyll config.
touch .nojekyll

# Commits the changes.
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
git add .
git commit -m "Documentation update." -a || true

- name: Push changes to gh-pages branch
uses: ad-m/github-push-action@master
with:
branch: gh-pages
github_token: ${{ secrets.GITHUB_TOKEN }}
force: True
- name: Checkout repository
uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'

- name: Install dependencies
run: |
pip install -e ."[docs]"

- name: Build documentation
run: mkdocs build

- name: Upload site artifact
uses: actions/upload-artifact@v4
with:
path: site

- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./site
force_orphan: true
6 changes: 2 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ instance/
# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/
# MkDocs build
/site

# PyBuilder
target/
Expand Down Expand Up @@ -105,8 +105,6 @@ venv.bak/
# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@ docs-install: ## install packages for local documentation.
pip install -e ."[docs]"

docs: ## builds the documentation.
$(MAKE) -C docs html
mkdocs build

docs-serve: ## serves the documentation on 127.0.0.1:8000.
$(MAKE) -C docs serve
mkdocs serve

new-env: ## creates environment for testing and documentation.
python -m venv env
Expand Down
55 changes: 31 additions & 24 deletions aioauth/collections.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
"""
.. code-block:: python

from aioauth import collections

Collections that are used throughout the project.

----
```python
from aioauth import collections
```
"""

from collections import UserDict
Expand All @@ -14,43 +11,53 @@

class HTTPHeaderDict(UserDict):
"""
:param headers:
An iterable of field-value pairs. Must not contain multiple field names
when compared case-insensitively.

:param kwargs:
Additional field-value pairs to pass in to ``dict.update``.
A dict-like container for storing HTTP headers with case-insensitive keys.

A ``dict`` like container for storing HTTP Headers.
Args:
headers (Optional[Mapping[str, str]]):
An iterable of field-value pairs. Must not contain duplicate field
names (case-insensitively).
**kwargs:
Additional key-value pairs passed to `dict.update`.

Example:

.. code-block:: python

```python
from aioauth.collections import HTTPHeaderDict
d = HTTPHeaderDict({"hello": "world"})
d['hello'] == 'world' # >>> True
d['Hello'] == 'world' # >>> True
d['hElLo'] == 'world' # >>> True
print(d['hello']) # >>> 'world'
print(d['Hello']) # >>> 'world'
print(d['hElLo']) # >>> 'world'
```
"""

def __init__(self, dict=None, **kwargs):
"""Object initialization."""
super().__init__(dict, **kwargs)
def __init__(self, headers=None, **kwargs):
"""Initialize the case-insensitive dictionary."""
super().__init__(headers, **kwargs)
self.data = {k.lower(): v for k, v in self.data.items()}

def __setitem__(self, key: str, value: str):
"""Set a key-value pair with case-insensitive key."""
super().__setitem__(key.lower(), value)

def __getitem__(self, key: str):
"""Retrieve a value by case-insensitive key."""
return super().__getitem__(key.lower())

def __delitem__(self, key: str):
"""Item deletion."""
"""Delete a key-value pair using a case-insensitive key."""
return super().__delitem__(key.lower())

def get(self, key: str, default: Any = None):
"""Case-insentive get."""
"""
Return the value for key if key is in the dictionary, else default.

Args:
key (str): The key to search for (case-insensitive).
default (Any, optional): The value to return if key is not found.

Returns:
Any: The value associated with the key, or default if not found.
"""
try:
return self[key]
except KeyError:
Expand Down
12 changes: 5 additions & 7 deletions aioauth/config.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
"""
.. code-block:: python

from aioauth import config

Configuration settings for aioauth server instance.

----
```python
from aioauth import config
```
"""

from dataclasses import dataclass
Expand Down Expand Up @@ -44,8 +42,8 @@ class Settings:

Reference links:

- https://datatracker.ietf.org/doc/html/rfc6749#section-4.2
- https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.2
- [https://datatracker.ietf.org/doc/html/rfc6749#section-4.2](https://datatracker.ietf.org/doc/html/rfc6749#section-4.2)
- [https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.2](https://datatracker.ietf.org/doc/html/rfc6749#section-4.2.2)
"""

AUTHORIZATION_CODE_EXPIRES_IN: int = 5 * 60
Expand Down
9 changes: 3 additions & 6 deletions aioauth/constances.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
"""
.. code-block:: python

from aioauth import constances

Constants that are used throughout the project.

----
```python
from aioauth import constances
```
"""

from .collections import HTTPHeaderDict
Expand Down
17 changes: 7 additions & 10 deletions aioauth/errors.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
"""
.. code-block:: python

from aioauth import errors

Errors used throughout the project.

----
```python
from aioauth import errors
```
"""

from http import HTTPStatus
Expand Down Expand Up @@ -77,11 +74,11 @@ class InvalidClientError(OAuth2Error):
"""
Client authentication failed (e.g. unknown client, no client
authentication included, or unsupported authentication method).
The authorization server **may** return an ``HTTP 401`` (Unauthorized) status
The authorization server **may** return an `HTTP 401` (Unauthorized) status
code to indicate which HTTP authentication schemes are supported.
If the client attempted to authenticate via the ``Authorization`` request
header field, the authorization server **must** respond with an
``HTTP 401`` (Unauthorized) status code, and include the ``WWW-Authenticate``
`HTTP 401` (Unauthorized) status code, and include the `WWW-Authenticate`
response header field matching the authentication scheme used by the
client.
"""
Expand Down Expand Up @@ -140,7 +137,7 @@ class InvalidGrantError(OAuth2Error):
not match the redirection URI used in the authorization request, or was
issued to another client.

See `RFC6749 section 5.2 <https://tools.ietf.org/html/rfc6749#section-5.2>`_.
See [RFC6749 section 5.2](https://tools.ietf.org/html/rfc6749#section-5.2).
"""

error: ErrorType = "invalid_grant"
Expand All @@ -167,7 +164,7 @@ class InvalidScopeError(OAuth2Error):
The requested scope is invalid, unknown, or malformed, or
exceeds the scope granted by the resource owner.

See `RFC6749 section 5.2 <https://tools.ietf.org/html/rfc6749#section-5.2>`_.
See [RFC6749 section 5.2](https://tools.ietf.org/html/rfc6749#section-5.2).
"""

error: ErrorType = "invalid_scope"
Expand Down
23 changes: 10 additions & 13 deletions aioauth/grant_type.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
"""
.. code-block:: python

from aioauth import grant_type

Different OAuth 2.0 grant types.

----
```python
from aioauth import grant_type
```
"""

from typing import Optional
Expand Down Expand Up @@ -92,13 +89,13 @@ class AuthorizationCodeGrantType(GrantTypeBase):
the user returns to the client via the redirect URL, the application
will get the authorization code from the URL and use it to request
an access token.
It is recommended that all clients use `RFC 7636 <https://tools.ietf.org/html/rfc7636>`_
It is recommended that all clients use [RFC 7636](https://tools.ietf.org/html/rfc7636)
Proof Key for Code Exchange extension with this flow as well to
provide better security.

Note:
Note that ``aioauth`` implements RFC 7636 out-of-the-box.
See `RFC 6749 section 1.3.1 <https://tools.ietf.org/html/rfc6749#section-1.3.1>`_.
Note that `aioauth` implements RFC 7636 out-of-the-box.
See [RFC 6749 section 1.3.1](https://tools.ietf.org/html/rfc6749#section-1.3.1).
"""

async def validate_request(self, request: Request) -> Client:
Expand Down Expand Up @@ -170,8 +167,8 @@ class PasswordGrantType(GrantTypeBase):
for an access token. Because the client application has to collect
the user's password and send it to the authorization server, it is
not recommended that this grant be used at all anymore.
See `RFC 6749 section 1.3.3 <https://tools.ietf.org/html/rfc6749#section-1.3.3>`_.
The latest `OAuth 2.0 Security Best Current Practice <https://tools.ietf.org/html/draft-ietf-oauth-security-topics-13#section-3.4>`_
See [RFC 6749 section 1.3.3](https://tools.ietf.org/html/rfc6749#section-1.3.3).
The latest [OAuth 2.0 Security Best Current Practice](https://tools.ietf.org/html/draft-ietf-oauth-security-topics-13#section-3.4)
disallows the password grant entirely.
"""

Expand Down Expand Up @@ -199,7 +196,7 @@ class RefreshTokenGrantType(GrantTypeBase):
refresh token for an access token when the access token has expired.
This allows clients to continue to have a valid access token without
further interaction with the user.
See `RFC 6749 section 1.5 <https://tools.ietf.org/html/rfc6749#section-1.5>`_.
See [RFC 6749 section 1.5](https://tools.ietf.org/html/rfc6749#section-1.5).
"""

async def create_token_response(
Expand Down Expand Up @@ -272,7 +269,7 @@ class ClientCredentialsGrantType(GrantTypeBase):
access token outside of the context of a user. This is typically
used by clients to access resources about themselves rather than to
access a user's resources.
See `RFC 6749 section 4.4 <https://tools.ietf.org/html/rfc6749#section-4.4>`_.
See [RFC 6749 section 4.4](https://tools.ietf.org/html/rfc6749#section-4.4).
"""

async def validate_request(self, request: Request) -> Client:
Expand Down
Loading