Skip to content

Commit

Permalink
Allauth: add SAML integration (#11262)
Browse files Browse the repository at this point in the history
* Allauth: add SAML integration

This is mostly the basics to get SAML working,
the actual implementation code will live in .com.

Ref readthedocs/readthedocs-corporate#1740.

* Fix template comment

* Add model

* Fix checks

* Add product type

* Update from review
  • Loading branch information
stsewd committed May 15, 2024
1 parent c160859 commit a6130d3
Show file tree
Hide file tree
Showing 10 changed files with 125 additions and 22 deletions.
10 changes: 10 additions & 0 deletions dockerfiles/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ RUN apt-get -y install \
npm \
rclone

# Dependencies for django-allauth SAML support.
# See:
# - https://github.com/SAML-Toolkits/python3-saml#installation
# - https://github.com/xmlsec/python-xmlsec#linux-debian
RUN apt-get -y install \
pkg-config \
libxml2-dev \
libxmlsec1-dev \
libxmlsec1-openssl

# Gets the MinIO mc client used to add buckets upon initialization
# If this client should have issues running inside this image, it is also
# fine to defer it to a separate image.
Expand Down
35 changes: 35 additions & 0 deletions readthedocs/sso/migrations/0002_add_saml_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Generated by Django 4.2.11 on 2024-04-04 20:32

import django.db.models.deletion
from django.db import migrations, models
from django_safemigrate import Safe


class Migration(migrations.Migration):
safe = Safe.before_deploy
dependencies = [
("socialaccount", "0005_socialtoken_nullable_app"),
("sso", "0001_squashed"),
]

operations = [
migrations.AddField(
model_name="ssointegration",
name="saml_app",
field=models.OneToOneField(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="sso_integration",
to="socialaccount.socialapp",
),
),
migrations.AlterField(
model_name="ssointegration",
name="provider",
field=models.CharField(
choices=[("allauth", "AllAuth"), ("email", "Email"), ("saml", "SAML")],
max_length=32,
),
),
]
11 changes: 11 additions & 0 deletions readthedocs/sso/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ class SSOIntegration(models.Model):

PROVIDER_ALLAUTH = "allauth"
PROVIDER_EMAIL = "email"
PROVIDER_SAML = "saml"
PROVIDER_CHOICES = (
(PROVIDER_ALLAUTH, "AllAuth"),
(PROVIDER_EMAIL, "Email"),
(PROVIDER_SAML, "SAML"),
)

name = models.CharField(
Expand All @@ -36,6 +38,15 @@ class SSOIntegration(models.Model):
choices=PROVIDER_CHOICES,
max_length=32,
)

saml_app = models.OneToOneField(
"socialaccount.SocialApp",
related_name="sso_integration",
on_delete=models.CASCADE,
null=True,
blank=True,
)

domains = models.ManyToManyField(
"sso.SSODomain",
related_name="ssointegrations",
Expand Down
2 changes: 2 additions & 0 deletions readthedocs/subscriptions/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
TYPE_AUDIT_LOGS = "audit-logs"
TYPE_AUDIT_PAGEVIEWS = "audit-pageviews"
TYPE_REDIRECTS_LIMIT = "redirects-limit"
TYPE_SSO_SAML = "sso-saml"

FEATURE_TYPES = (
(TYPE_CNAME, _("Custom domain")),
Expand All @@ -31,6 +32,7 @@
(TYPE_PAGEVIEW_ANALYTICS, _("Pageview analytics")),
(TYPE_CONCURRENT_BUILDS, _("Concurrent builds")),
(TYPE_SSO, _("Single sign on (SSO) with Google")),
(TYPE_SSO_SAML, _("Single sign on (SSO) with SAML")),
(TYPE_CUSTOM_URL, _("Custom URLs")),
(TYPE_AUDIT_LOGS, _("Audit logs")),
(TYPE_AUDIT_PAGEVIEWS, _("Audit logs for every page view")),
Expand Down
22 changes: 6 additions & 16 deletions readthedocs/templates/socialaccount/snippets/provider_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,12 @@
{% get_providers as socialaccount_providers %}

{% for provider in socialaccount_providers %}
{% if provider.id == "openid" %}
{% for brand in provider.get_brands %}
<li>
<form action="{% provider_login_url provider.id openid=brand.openid_url process=process next=next %}" method="post">
{% csrf_token %}
<button
class="socialaccount-provider {{ provider.id }} {{ brand.id }} button"
type="submit"
title="{{ brand.name }}">
{% trans verbiage|default:'Connect to' %} {{ brand.name }}
</button>
</form>
</li>
{% endfor %}
{% endif %}
{% if provider.id != 'bitbucket' %}
{% comment %}
- OpenID is not implemented.
- Bitbucket is deprecated (in favor of their new oauth implementation).
- SAML is handled in another view, we don't want to list all SAML integrations here.
{% endcomment %}
{% if provider.id != 'bitbucket' and provider.id != 'saml' %}
{% if allowed_providers and provider.id in allowed_providers or not allowed_providers %}
<li>
<form action="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params next=next %}" method="post">
Expand Down
17 changes: 16 additions & 1 deletion requirements/deploy.txt
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ django==4.2.13
# django-timezone-field
# djangorestframework
# jsonfield
django-allauth==0.57.2
django-allauth[saml]==0.57.2
# via -r requirements/pip.txt
django-annoying==0.10.6
# via -r requirements/pip.txt
Expand Down Expand Up @@ -225,6 +225,10 @@ idna==3.7
# requests
ipython==8.24.0
# via -r requirements/deploy.in
isodate==0.6.1
# via
# -r requirements/pip.txt
# python3-saml
jedi==0.19.1
# via ipython
jmespath==1.0.1
Expand Down Expand Up @@ -252,6 +256,8 @@ lxml==5.2.1
# via
# -r requirements/pip.txt
# pyquery
# python3-saml
# xmlsec
markdown==3.6
# via -r requirements/pip.txt
matplotlib-inline==0.1.7
Expand Down Expand Up @@ -318,6 +324,10 @@ python3-openid==3.2.0
# via
# -r requirements/pip.txt
# django-allauth
python3-saml==1.16.0
# via
# -r requirements/pip.txt
# django-allauth
pytz==2024.1
# via
# -r requirements/pip.txt
Expand Down Expand Up @@ -361,6 +371,7 @@ six==1.16.0
# asttokens
# django-annoying
# django-elasticsearch-dsl
# isodate
# python-dateutil
# unicode-slugify
slumber==0.7.1
Expand Down Expand Up @@ -440,3 +451,7 @@ websocket-client==1.8.0
# via
# -r requirements/pip.txt
# docker
xmlsec==1.3.13
# via
# -r requirements/pip.txt
# python3-saml
17 changes: 16 additions & 1 deletion requirements/docker.txt
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ django==4.2.13
# django-timezone-field
# djangorestframework
# jsonfield
django-allauth==0.57.2
django-allauth[saml]==0.57.2
# via -r requirements/pip.txt
django-annoying==0.10.6
# via -r requirements/pip.txt
Expand Down Expand Up @@ -240,6 +240,10 @@ ipdb==0.13.13
# via -r requirements/docker.in
ipython==8.24.0
# via ipdb
isodate==0.6.1
# via
# -r requirements/pip.txt
# python3-saml
jedi==0.19.1
# via ipython
jmespath==1.0.1
Expand Down Expand Up @@ -267,6 +271,8 @@ lxml==5.2.1
# via
# -r requirements/pip.txt
# pyquery
# python3-saml
# xmlsec
markdown==3.6
# via -r requirements/pip.txt
markdown-it-py==3.0.0
Expand Down Expand Up @@ -350,6 +356,10 @@ python3-openid==3.2.0
# via
# -r requirements/pip.txt
# django-allauth
python3-saml==1.16.0
# via
# -r requirements/pip.txt
# django-allauth
pytz==2024.1
# via
# -r requirements/pip.txt
Expand Down Expand Up @@ -393,6 +403,7 @@ six==1.16.0
# asttokens
# django-annoying
# django-elasticsearch-dsl
# isodate
# python-dateutil
# unicode-slugify
slumber==0.7.1
Expand Down Expand Up @@ -477,3 +488,7 @@ websocket-client==1.8.0
# docker
wmctrl==0.5
# via pdbpp
xmlsec==1.3.13
# via
# -r requirements/pip.txt
# python3-saml
2 changes: 1 addition & 1 deletion requirements/pip.in
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ tzdata

# 0.58.0 refactored the built-in templates,
# we need to check if we need to update our custom templates.
django-allauth==0.57.2
django-allauth[saml]==0.57.2

requests-oauthlib

Expand Down
14 changes: 12 additions & 2 deletions requirements/pip.txt
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ django==4.2.13
# django-timezone-field
# djangorestframework
# jsonfield
django-allauth==0.57.2
django-allauth[saml]==0.57.2
# via -r requirements/pip.in
django-annoying==0.10.6
# via -r requirements/pip.in
Expand Down Expand Up @@ -170,6 +170,8 @@ gunicorn==22.0.0
# via -r requirements/pip.in
idna==3.7
# via requests
isodate==0.6.1
# via python3-saml
jmespath==1.0.1
# via
# boto3
Expand All @@ -185,7 +187,10 @@ lexid==2021.1006
looseversion==1.3.0
# via bumpver
lxml==5.2.1
# via pyquery
# via
# pyquery
# python3-saml
# xmlsec
markdown==3.6
# via -r requirements/pip.in
oauthlib==3.2.2
Expand Down Expand Up @@ -223,6 +228,8 @@ python-dateutil==2.9.0.post0
# python-crontab
python3-openid==3.2.0
# via django-allauth
python3-saml==1.16.0
# via django-allauth
pytz==2024.1
# via
# -r requirements/pip.in
Expand Down Expand Up @@ -260,6 +267,7 @@ six==1.16.0
# via
# django-annoying
# django-elasticsearch-dsl
# isodate
# python-dateutil
# unicode-slugify
slumber==0.7.1
Expand Down Expand Up @@ -311,6 +319,8 @@ wcwidth==0.2.13
# via prompt-toolkit
websocket-client==1.8.0
# via docker
xmlsec==1.3.13
# via python3-saml

# The following packages are considered to be unsafe in a requirements file:
# pip
17 changes: 16 additions & 1 deletion requirements/testing.txt
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ django==4.2.13
# django-timezone-field
# djangorestframework
# jsonfield
django-allauth==0.57.2
django-allauth[saml]==0.57.2
# via -r requirements/pip.txt
django-annoying==0.10.6
# via -r requirements/pip.txt
Expand Down Expand Up @@ -230,6 +230,10 @@ imagesize==1.4.1
# via sphinx
iniconfig==2.0.0
# via pytest
isodate==0.6.1
# via
# -r requirements/pip.txt
# python3-saml
jinja2==3.1.4
# via sphinx
jmespath==1.0.1
Expand Down Expand Up @@ -257,6 +261,8 @@ lxml==5.2.1
# via
# -r requirements/pip.txt
# pyquery
# python3-saml
# xmlsec
markdown==3.6
# via -r requirements/pip.txt
markupsafe==2.1.5
Expand Down Expand Up @@ -333,6 +339,10 @@ python3-openid==3.2.0
# via
# -r requirements/pip.txt
# django-allauth
python3-saml==1.16.0
# via
# -r requirements/pip.txt
# django-allauth
pytz==2024.1
# via
# -r requirements/pip.txt
Expand Down Expand Up @@ -379,6 +389,7 @@ six==1.16.0
# -r requirements/pip.txt
# django-annoying
# django-elasticsearch-dsl
# isodate
# python-dateutil
# unicode-slugify
slumber==0.7.1
Expand Down Expand Up @@ -466,5 +477,9 @@ websocket-client==1.8.0
# via
# -r requirements/pip.txt
# docker
xmlsec==1.3.13
# via
# -r requirements/pip.txt
# python3-saml
yamale==2.2.0
# via -r requirements/testing.in

0 comments on commit a6130d3

Please sign in to comment.