Skip to content

Commit a6130d3

Browse files
authored
Allauth: add SAML integration (#11262)
* 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
1 parent c160859 commit a6130d3

File tree

10 files changed

+125
-22
lines changed

10 files changed

+125
-22
lines changed

dockerfiles/Dockerfile

+10
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ RUN apt-get -y install \
3333
npm \
3434
rclone
3535

36+
# Dependencies for django-allauth SAML support.
37+
# See:
38+
# - https://github.com/SAML-Toolkits/python3-saml#installation
39+
# - https://github.com/xmlsec/python-xmlsec#linux-debian
40+
RUN apt-get -y install \
41+
pkg-config \
42+
libxml2-dev \
43+
libxmlsec1-dev \
44+
libxmlsec1-openssl
45+
3646
# Gets the MinIO mc client used to add buckets upon initialization
3747
# If this client should have issues running inside this image, it is also
3848
# fine to defer it to a separate image.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Generated by Django 4.2.11 on 2024-04-04 20:32
2+
3+
import django.db.models.deletion
4+
from django.db import migrations, models
5+
from django_safemigrate import Safe
6+
7+
8+
class Migration(migrations.Migration):
9+
safe = Safe.before_deploy
10+
dependencies = [
11+
("socialaccount", "0005_socialtoken_nullable_app"),
12+
("sso", "0001_squashed"),
13+
]
14+
15+
operations = [
16+
migrations.AddField(
17+
model_name="ssointegration",
18+
name="saml_app",
19+
field=models.OneToOneField(
20+
blank=True,
21+
null=True,
22+
on_delete=django.db.models.deletion.CASCADE,
23+
related_name="sso_integration",
24+
to="socialaccount.socialapp",
25+
),
26+
),
27+
migrations.AlterField(
28+
model_name="ssointegration",
29+
name="provider",
30+
field=models.CharField(
31+
choices=[("allauth", "AllAuth"), ("email", "Email"), ("saml", "SAML")],
32+
max_length=32,
33+
),
34+
),
35+
]

readthedocs/sso/models.py

+11
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ class SSOIntegration(models.Model):
1313

1414
PROVIDER_ALLAUTH = "allauth"
1515
PROVIDER_EMAIL = "email"
16+
PROVIDER_SAML = "saml"
1617
PROVIDER_CHOICES = (
1718
(PROVIDER_ALLAUTH, "AllAuth"),
1819
(PROVIDER_EMAIL, "Email"),
20+
(PROVIDER_SAML, "SAML"),
1921
)
2022

2123
name = models.CharField(
@@ -36,6 +38,15 @@ class SSOIntegration(models.Model):
3638
choices=PROVIDER_CHOICES,
3739
max_length=32,
3840
)
41+
42+
saml_app = models.OneToOneField(
43+
"socialaccount.SocialApp",
44+
related_name="sso_integration",
45+
on_delete=models.CASCADE,
46+
null=True,
47+
blank=True,
48+
)
49+
3950
domains = models.ManyToManyField(
4051
"sso.SSODomain",
4152
related_name="ssointegrations",

readthedocs/subscriptions/constants.py

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
TYPE_AUDIT_LOGS = "audit-logs"
2020
TYPE_AUDIT_PAGEVIEWS = "audit-pageviews"
2121
TYPE_REDIRECTS_LIMIT = "redirects-limit"
22+
TYPE_SSO_SAML = "sso-saml"
2223

2324
FEATURE_TYPES = (
2425
(TYPE_CNAME, _("Custom domain")),
@@ -31,6 +32,7 @@
3132
(TYPE_PAGEVIEW_ANALYTICS, _("Pageview analytics")),
3233
(TYPE_CONCURRENT_BUILDS, _("Concurrent builds")),
3334
(TYPE_SSO, _("Single sign on (SSO) with Google")),
35+
(TYPE_SSO_SAML, _("Single sign on (SSO) with SAML")),
3436
(TYPE_CUSTOM_URL, _("Custom URLs")),
3537
(TYPE_AUDIT_LOGS, _("Audit logs")),
3638
(TYPE_AUDIT_PAGEVIEWS, _("Audit logs for every page view")),

readthedocs/templates/socialaccount/snippets/provider_list.html

+6-16
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,12 @@
44
{% get_providers as socialaccount_providers %}
55

66
{% for provider in socialaccount_providers %}
7-
{% if provider.id == "openid" %}
8-
{% for brand in provider.get_brands %}
9-
<li>
10-
<form action="{% provider_login_url provider.id openid=brand.openid_url process=process next=next %}" method="post">
11-
{% csrf_token %}
12-
<button
13-
class="socialaccount-provider {{ provider.id }} {{ brand.id }} button"
14-
type="submit"
15-
title="{{ brand.name }}">
16-
{% trans verbiage|default:'Connect to' %} {{ brand.name }}
17-
</button>
18-
</form>
19-
</li>
20-
{% endfor %}
21-
{% endif %}
22-
{% if provider.id != 'bitbucket' %}
7+
{% comment %}
8+
- OpenID is not implemented.
9+
- Bitbucket is deprecated (in favor of their new oauth implementation).
10+
- SAML is handled in another view, we don't want to list all SAML integrations here.
11+
{% endcomment %}
12+
{% if provider.id != 'bitbucket' and provider.id != 'saml' %}
2313
{% if allowed_providers and provider.id in allowed_providers or not allowed_providers %}
2414
<li>
2515
<form action="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params next=next %}" method="post">

requirements/deploy.txt

+16-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ django==4.2.13
124124
# django-timezone-field
125125
# djangorestframework
126126
# jsonfield
127-
django-allauth==0.57.2
127+
django-allauth[saml]==0.57.2
128128
# via -r requirements/pip.txt
129129
django-annoying==0.10.6
130130
# via -r requirements/pip.txt
@@ -225,6 +225,10 @@ idna==3.7
225225
# requests
226226
ipython==8.24.0
227227
# via -r requirements/deploy.in
228+
isodate==0.6.1
229+
# via
230+
# -r requirements/pip.txt
231+
# python3-saml
228232
jedi==0.19.1
229233
# via ipython
230234
jmespath==1.0.1
@@ -252,6 +256,8 @@ lxml==5.2.1
252256
# via
253257
# -r requirements/pip.txt
254258
# pyquery
259+
# python3-saml
260+
# xmlsec
255261
markdown==3.6
256262
# via -r requirements/pip.txt
257263
matplotlib-inline==0.1.7
@@ -318,6 +324,10 @@ python3-openid==3.2.0
318324
# via
319325
# -r requirements/pip.txt
320326
# django-allauth
327+
python3-saml==1.16.0
328+
# via
329+
# -r requirements/pip.txt
330+
# django-allauth
321331
pytz==2024.1
322332
# via
323333
# -r requirements/pip.txt
@@ -361,6 +371,7 @@ six==1.16.0
361371
# asttokens
362372
# django-annoying
363373
# django-elasticsearch-dsl
374+
# isodate
364375
# python-dateutil
365376
# unicode-slugify
366377
slumber==0.7.1
@@ -440,3 +451,7 @@ websocket-client==1.8.0
440451
# via
441452
# -r requirements/pip.txt
442453
# docker
454+
xmlsec==1.3.13
455+
# via
456+
# -r requirements/pip.txt
457+
# python3-saml

requirements/docker.txt

+16-1
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ django==4.2.13
134134
# django-timezone-field
135135
# djangorestframework
136136
# jsonfield
137-
django-allauth==0.57.2
137+
django-allauth[saml]==0.57.2
138138
# via -r requirements/pip.txt
139139
django-annoying==0.10.6
140140
# via -r requirements/pip.txt
@@ -240,6 +240,10 @@ ipdb==0.13.13
240240
# via -r requirements/docker.in
241241
ipython==8.24.0
242242
# via ipdb
243+
isodate==0.6.1
244+
# via
245+
# -r requirements/pip.txt
246+
# python3-saml
243247
jedi==0.19.1
244248
# via ipython
245249
jmespath==1.0.1
@@ -267,6 +271,8 @@ lxml==5.2.1
267271
# via
268272
# -r requirements/pip.txt
269273
# pyquery
274+
# python3-saml
275+
# xmlsec
270276
markdown==3.6
271277
# via -r requirements/pip.txt
272278
markdown-it-py==3.0.0
@@ -350,6 +356,10 @@ python3-openid==3.2.0
350356
# via
351357
# -r requirements/pip.txt
352358
# django-allauth
359+
python3-saml==1.16.0
360+
# via
361+
# -r requirements/pip.txt
362+
# django-allauth
353363
pytz==2024.1
354364
# via
355365
# -r requirements/pip.txt
@@ -393,6 +403,7 @@ six==1.16.0
393403
# asttokens
394404
# django-annoying
395405
# django-elasticsearch-dsl
406+
# isodate
396407
# python-dateutil
397408
# unicode-slugify
398409
slumber==0.7.1
@@ -477,3 +488,7 @@ websocket-client==1.8.0
477488
# docker
478489
wmctrl==0.5
479490
# via pdbpp
491+
xmlsec==1.3.13
492+
# via
493+
# -r requirements/pip.txt
494+
# python3-saml

requirements/pip.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ tzdata
8484

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

8989
requests-oauthlib
9090

requirements/pip.txt

+12-2
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ django==4.2.13
8585
# django-timezone-field
8686
# djangorestframework
8787
# jsonfield
88-
django-allauth==0.57.2
88+
django-allauth[saml]==0.57.2
8989
# via -r requirements/pip.in
9090
django-annoying==0.10.6
9191
# via -r requirements/pip.in
@@ -170,6 +170,8 @@ gunicorn==22.0.0
170170
# via -r requirements/pip.in
171171
idna==3.7
172172
# via requests
173+
isodate==0.6.1
174+
# via python3-saml
173175
jmespath==1.0.1
174176
# via
175177
# boto3
@@ -185,7 +187,10 @@ lexid==2021.1006
185187
looseversion==1.3.0
186188
# via bumpver
187189
lxml==5.2.1
188-
# via pyquery
190+
# via
191+
# pyquery
192+
# python3-saml
193+
# xmlsec
189194
markdown==3.6
190195
# via -r requirements/pip.in
191196
oauthlib==3.2.2
@@ -223,6 +228,8 @@ python-dateutil==2.9.0.post0
223228
# python-crontab
224229
python3-openid==3.2.0
225230
# via django-allauth
231+
python3-saml==1.16.0
232+
# via django-allauth
226233
pytz==2024.1
227234
# via
228235
# -r requirements/pip.in
@@ -260,6 +267,7 @@ six==1.16.0
260267
# via
261268
# django-annoying
262269
# django-elasticsearch-dsl
270+
# isodate
263271
# python-dateutil
264272
# unicode-slugify
265273
slumber==0.7.1
@@ -311,6 +319,8 @@ wcwidth==0.2.13
311319
# via prompt-toolkit
312320
websocket-client==1.8.0
313321
# via docker
322+
xmlsec==1.3.13
323+
# via python3-saml
314324

315325
# The following packages are considered to be unsafe in a requirements file:
316326
# pip

requirements/testing.txt

+16-1
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ django==4.2.13
125125
# django-timezone-field
126126
# djangorestframework
127127
# jsonfield
128-
django-allauth==0.57.2
128+
django-allauth[saml]==0.57.2
129129
# via -r requirements/pip.txt
130130
django-annoying==0.10.6
131131
# via -r requirements/pip.txt
@@ -230,6 +230,10 @@ imagesize==1.4.1
230230
# via sphinx
231231
iniconfig==2.0.0
232232
# via pytest
233+
isodate==0.6.1
234+
# via
235+
# -r requirements/pip.txt
236+
# python3-saml
233237
jinja2==3.1.4
234238
# via sphinx
235239
jmespath==1.0.1
@@ -257,6 +261,8 @@ lxml==5.2.1
257261
# via
258262
# -r requirements/pip.txt
259263
# pyquery
264+
# python3-saml
265+
# xmlsec
260266
markdown==3.6
261267
# via -r requirements/pip.txt
262268
markupsafe==2.1.5
@@ -333,6 +339,10 @@ python3-openid==3.2.0
333339
# via
334340
# -r requirements/pip.txt
335341
# django-allauth
342+
python3-saml==1.16.0
343+
# via
344+
# -r requirements/pip.txt
345+
# django-allauth
336346
pytz==2024.1
337347
# via
338348
# -r requirements/pip.txt
@@ -379,6 +389,7 @@ six==1.16.0
379389
# -r requirements/pip.txt
380390
# django-annoying
381391
# django-elasticsearch-dsl
392+
# isodate
382393
# python-dateutil
383394
# unicode-slugify
384395
slumber==0.7.1
@@ -466,5 +477,9 @@ websocket-client==1.8.0
466477
# via
467478
# -r requirements/pip.txt
468479
# docker
480+
xmlsec==1.3.13
481+
# via
482+
# -r requirements/pip.txt
483+
# python3-saml
469484
yamale==2.2.0
470485
# via -r requirements/testing.in

0 commit comments

Comments
 (0)