diff --git a/config/settings.py b/config/settings.py
index c9d486a5..ff4ced9a 100755
--- a/config/settings.py
+++ b/config/settings.py
@@ -67,6 +67,8 @@ INSTALLED_APPS += [
"health_check.db",
"health_check.contrib.celery",
"imagekit",
+ # Allows authentication for Mailman
+ "oauth2_provider",
# Allauth dependencies:
"allauth",
"allauth.account",
@@ -107,6 +109,7 @@ MIDDLEWARE = [
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
+ "oauth2_provider.middleware.OAuth2TokenMiddleware",
]
if DEBUG:
@@ -286,7 +289,10 @@ HAYSTACK_CONNECTIONS = {
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"
-AUTHENTICATION_BACKENDS = ("allauth.account.auth_backends.AuthenticationBackend",)
+AUTHENTICATION_BACKENDS = (
+ "allauth.account.auth_backends.AuthenticationBackend",
+ "oauth2_provider.backends.OAuth2Backend",
+)
# GitHub settings
@@ -472,3 +478,8 @@ BOOST_CALENDAR = "5rorfm42nvmpt77ac0vult9iig@group.calendar.google.com"
CALENDAR_API_KEY = env("CALENDAR_API_KEY", default="changeme")
EVENTS_CACHE_KEY = "homepage_events"
EVENTS_CACHE_TIMEOUT = 300 # 5 min
+
+# OAuth settings
+OAUTH_APP_NAME = (
+ "Boost OAuth Concept" # Stored in the admin; replicated for convenience
+)
diff --git a/config/urls.py b/config/urls.py
index 1701e8c0..9c417009 100755
--- a/config/urls.py
+++ b/config/urls.py
@@ -75,6 +75,7 @@ urlpatterns = (
path("", HomepageView.as_view(), name="home"),
path("homepage-beta/", HomepageBetaView.as_view(), name="home-beta"),
path("admin/", admin.site.urls),
+ path("oauth2/", include("oauth2_provider.urls", namespace="oauth2_provider")),
path("feed/downloads.rss", RSSVersionFeed(), name="downloads_feed_rss"),
path("feed/downloads.atom", AtomVersionFeed(), name="downloads_feed_atom"),
path("feed/news.rss", RSSNewsFeed(), name="news_feed_rss"),
diff --git a/core/oauthhelper.py b/core/oauthhelper.py
new file mode 100644
index 00000000..2c6fa248
--- /dev/null
+++ b/core/oauthhelper.py
@@ -0,0 +1,10 @@
+from django.conf import settings
+
+from oauth2_provider.models import Application
+
+
+def get_oauth_client():
+ try:
+ return Application.objects.get(name=settings.OAUTH_APP_NAME)
+ except Application.DoesNotExist:
+ return
diff --git a/core/views.py b/core/views.py
index 527db983..ab9424ff 100644
--- a/core/views.py
+++ b/core/views.py
@@ -13,6 +13,7 @@ from django.template.loader import render_to_string
from django.views import View
from django.views.generic import TemplateView
+
from .asciidoc import process_adoc_to_html_content
from .boostrenderer import get_content_from_s3
from .htmlhelper import modernize_legacy_page
diff --git a/docs/README.md b/docs/README.md
index 19fc9234..600ca72d 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,6 +1,7 @@
# Documentation for the Boost Website
- [API Documentation](./api.md) - We don't have many API endpoints, but the ones we do have are documented here
+- [Boost Mailing List](./mailing_list.md) -Includes OAuth instructions
- [Caching and the `RenderedContent` model](./caching_rendered_content.md)
- [Dependency Management](./dependencies.md)
- [Development Setup Notes](./development_setup_notes.md)
diff --git a/docs/mailing_list.md b/docs/mailing_list.md
new file mode 100644
index 00000000..f9f1a1f1
--- /dev/null
+++ b/docs/mailing_list.md
@@ -0,0 +1,18 @@
+# Boost Mailing List
+
+Access to the mailing list is granted to Boost users via OAuth using `django-oauth-toolkit`.
+
+## Setting Up Boost as an OAuth identity provider
+
+1. In the admin `/admin/`, create a new **Application** under the **Django OAuth Toolkit** heading.
+
+
+
+2. Fill in the form:
+
+
+ - The `client_id` and `client_secret` will fill in automatically. Make sure to copy and paste them somewhere before you save.
+ - Add the `redirect_uri` (this URL will be from the project that hosts the mailing list, and is not in **this** project. A sample URL for testing is `https://www.getpostman.com/oauth2/callback`.)
+ - Whatever is in the `name` field will be what the user sees on the authorization screen.
+ - Select **confidential** as the client type, and **Authorization code** as the grant type.
+ - Save the record.
diff --git a/requirements.in b/requirements.in
index 7190ecf1..e757ed29 100755
--- a/requirements.in
+++ b/requirements.in
@@ -8,6 +8,7 @@ django-db-geventpool
django-extensions
django-health-check
django-imagekit
+django-oauth-toolkit
django-redis
django-rest-auth
django-widget-tweaks
diff --git a/requirements.txt b/requirements.txt
index b5e5044e..ed74faee 100755
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,14 +2,12 @@
# This file is autogenerated by pip-compile with Python 3.11
# by the following command:
#
-# pip-compile
+# pip-compile --output-file=./requirements.txt ./requirements.in
#
amqp==5.1.1
# via kombu
appdirs==1.4.4
# via fs
-appnope==0.1.3
- # via ipython
asgiref==3.7.2
# via django
asttokens==2.2.1
@@ -19,14 +17,14 @@ async-timeout==4.0.2
backcall==0.2.0
# via ipython
beautifulsoup4==4.12.2
- # via -r requirements.in
+ # via -r ./requirements.in
billiard==3.6.4.0
# via celery
black==23.3.0
- # via -r requirements.in
+ # via -r ./requirements.in
boto3==1.26.154
# via
- # -r requirements.in
+ # -r ./requirements.in
# django-bakery
botocore==1.29.154
# via
@@ -37,9 +35,9 @@ build==0.10.0
bump2version==1.0.1
# via bumpversion
bumpversion==0.6.0
- # via -r requirements.in
+ # via -r ./requirements.in
celery==5.2.7
- # via -r requirements.in
+ # via -r ./requirements.in
certifi==2023.5.7
# via
# elasticsearch
@@ -70,12 +68,16 @@ coverage[toml]==7.2.7
# via pytest-cov
cryptography==41.0.1
# via
- # -r requirements.in
+ # -r ./requirements.in
+ # django-anymail
+ # jwcrypto
# pyjwt
decorator==5.1.1
# via ipython
defusedxml==0.7.1
# via python3-openid
+deprecated==1.2.14
+ # via jwcrypto
distlib==0.3.6
# via virtualenv
dj-database-url==2.0.0
@@ -84,7 +86,7 @@ dj-email-url==1.0.6
# via environs
django==4.2.2
# via
- # -r requirements.in
+ # -r ./requirements.in
# dj-database-url
# django-allauth
# django-anymail
@@ -95,65 +97,68 @@ django==4.2.2
# django-haystack
# django-health-check
# django-js-asset
+ # django-oauth-toolkit
# django-redis
# django-rest-auth
# django-storages
# djangorestframework
# model-bakery
django-admin-env-notice==1.0
- # via -r requirements.in
+ # via -r ./requirements.in
django-allauth==0.53.1
- # via -r requirements.in
+ # via -r ./requirements.in
django-anymail[mailgun]==10.0
- # via -r requirements.in
+ # via -r ./requirements.in
django-appconf==1.0.6
# via django-imagekit
django-bakery==0.13.2
- # via -r requirements.in
+ # via -r ./requirements.in
django-cache-url==3.4.4
# via environs
django-click==2.3.0
- # via -r requirements.in
+ # via -r ./requirements.in
django-cors-headers==4.0.0
- # via -r requirements.in
+ # via -r ./requirements.in
django-db-geventpool==4.0.1
- # via -r requirements.in
+ # via -r ./requirements.in
django-extensions==3.2.3
- # via -r requirements.in
+ # via -r ./requirements.in
django-haystack==3.2.1
- # via -r requirements.in
+ # via -r ./requirements.in
django-health-check==3.17.0
- # via -r requirements.in
+ # via -r ./requirements.in
django-imagekit==5.0.0
- # via -r requirements.in
+ # via -r ./requirements.in
django-js-asset==2.0.0
# via django-mptt
django-mptt==0.14
- # via -r requirements.in
+ # via -r ./requirements.in
+django-oauth-toolkit==2.3.0
+ # via -r ./requirements.in
django-redis==5.3.0
- # via -r requirements.in
+ # via -r ./requirements.in
django-rest-auth==0.9.5
- # via -r requirements.in
+ # via -r ./requirements.in
django-storages==1.13.2
- # via -r requirements.in
+ # via -r ./requirements.in
django-test-plus==2.2.1
- # via -r requirements.in
+ # via -r ./requirements.in
django-tracer==0.9.3
- # via -r requirements.in
+ # via -r ./requirements.in
django-widget-tweaks==1.4.12
- # via -r requirements.in
+ # via -r ./requirements.in
djangorestframework==3.14.0
# via
- # -r requirements.in
+ # -r ./requirements.in
# django-rest-auth
elasticsearch==7.17.9
- # via -r requirements.in
+ # via -r ./requirements.in
environs[django]==9.5.0
- # via -r requirements.in
+ # via -r ./requirements.in
executing==1.2.0
# via stack-data
faker==18.10.1
- # via -r requirements.in
+ # via -r ./requirements.in
fastcore==1.5.29
# via ghapi
filelock==3.12.2
@@ -161,15 +166,15 @@ filelock==3.12.2
fs==2.4.16
# via django-bakery
gevent==22.10.2
- # via -r requirements.in
+ # via -r ./requirements.in
ghapi==1.0.4
- # via -r requirements.in
+ # via -r ./requirements.in
greenlet==2.0.1
# via
- # -r requirements.in
+ # -r ./requirements.in
# gevent
gunicorn==20.1.0
- # via -r requirements.in
+ # via -r ./requirements.in
identify==2.5.24
# via pre-commit
idna==3.4
@@ -177,13 +182,15 @@ idna==3.4
iniconfig==2.0.0
# via pytest
ipython==8.14.0
- # via -r requirements.in
+ # via -r ./requirements.in
jedi==0.18.2
# via ipython
jmespath==1.0.1
# via
# boto3
# botocore
+jwcrypto==1.5.1
+ # via django-oauth-toolkit
kombu==5.3.1
# via celery
marshmallow==3.19.0
@@ -191,17 +198,19 @@ marshmallow==3.19.0
matplotlib-inline==0.1.6
# via ipython
minio==7.1.15
- # via -r requirements.in
+ # via -r ./requirements.in
mistletoe==1.1.0
- # via -r requirements.in
+ # via -r ./requirements.in
model-bakery==1.12.0
- # via -r requirements.in
+ # via -r ./requirements.in
mypy-extensions==1.0.0
# via black
nodeenv==1.8.0
# via pre-commit
oauthlib==3.2.2
- # via requests-oauthlib
+ # via
+ # django-oauth-toolkit
+ # requests-oauthlib
packaging==23.1
# via
# black
@@ -222,10 +231,10 @@ pilkit==3.0
# via django-imagekit
pillow==9.4.0
# via
- # -r requirements.in
+ # -r ./requirements.in
# pilkit
pip-tools==6.13.0
- # via -r requirements.in
+ # via -r ./requirements.in
platformdirs==3.5.3
# via
# black
@@ -233,7 +242,7 @@ platformdirs==3.5.3
pluggy==1.0.0
# via pytest
pre-commit==3.3.3
- # via -r requirements.in
+ # via -r ./requirements.in
prompt-toolkit==3.0.38
# via
# click-repl
@@ -241,7 +250,7 @@ prompt-toolkit==3.0.38
psycogreen==1.0.2
# via django-db-geventpool
psycopg2-binary==2.9.6
- # via -r requirements.in
+ # via -r ./requirements.in
ptyprocess==0.7.0
# via pexpect
pure-eval==0.2.2
@@ -256,24 +265,24 @@ pyproject-hooks==1.0.0
# via build
pytest==7.3.2
# via
- # -r requirements.in
+ # -r ./requirements.in
# pytest-cov
# pytest-django
pytest-cov==4.1.0
- # via -r requirements.in
+ # via -r ./requirements.in
pytest-django==4.5.2
- # via -r requirements.in
+ # via -r ./requirements.in
python-dateutil==2.8.2
# via
- # -r requirements.in
+ # -r ./requirements.in
# botocore
# faker
python-dotenv==1.0.0
# via environs
python-frontmatter==1.0.0
- # via -r requirements.in
+ # via -r ./requirements.in
python-json-logger==2.0.7
- # via -r requirements.in
+ # via -r ./requirements.in
python3-openid==3.2.0
# via django-allauth
pytz==2023.3
@@ -287,23 +296,25 @@ pyyaml==6.0
# responses
redis==4.5.4
# via
- # -r requirements.in
+ # -r ./requirements.in
# django-redis
requests==2.31.0
# via
- # -r requirements.in
+ # -r ./requirements.in
# django-allauth
# django-anymail
+ # django-oauth-toolkit
# requests-oauthlib
# responses
requests-oauthlib==1.3.1
# via django-allauth
responses==0.23.1
- # via -r requirements.in
+ # via -r ./requirements.in
s3transfer==0.6.1
# via boto3
six==1.16.0
# via
+ # asttokens
# django-bakery
# django-rest-auth
# fs
@@ -315,7 +326,7 @@ sqlparse==0.4.4
stack-data==0.6.2
# via ipython
structlog==23.1.0
- # via -r requirements.in
+ # via -r ./requirements.in
traitlets==5.9.0
# via
# ipython
@@ -334,6 +345,7 @@ urllib3==1.26.16
# responses
vine==5.0.0
# via
+ # amqp
# celery
# kombu
virtualenv==20.23.0
@@ -342,10 +354,12 @@ wcwidth==0.2.6
# via prompt-toolkit
wheel==0.40.0
# via
- # -r requirements.in
+ # -r ./requirements.in
# pip-tools
whitenoise==6.5.0
- # via -r requirements.in
+ # via -r ./requirements.in
+wrapt==1.16.0
+ # via deprecated
zope-event==4.6
# via gevent
zope-interface==6.0
diff --git a/users/tasks.py b/users/tasks.py
index 85e12150..0adb9416 100644
--- a/users/tasks.py
+++ b/users/tasks.py
@@ -2,6 +2,9 @@ import structlog
from django.contrib.auth import get_user_model
+from celery import shared_task
+from oauth2_provider.models import clear_expired
+
from config.celery import app
from core.githubhelper import GithubAPIClient
@@ -31,3 +34,12 @@ def update_user_github_photo(user_pk):
avatar_url = response["avatar_url"]
user.save_image_from_github(avatar_url)
logger.info("users_tasks_update_gh_photo_finished", user_pk=user_pk)
+
+
+# OAuth2 Tasks
+
+
+@shared_task
+def clear_tokens():
+ """Clears all expired tokens"""
+ clear_expired()