Skip to content

Commit 2246320

Browse files
committed
Merge branch 'main' of github.com:readthedocs/readthedocs.org into humitos/output-variable
2 parents 7b19e28 + 239d890 commit 2246320

File tree

10 files changed

+131
-21
lines changed

10 files changed

+131
-21
lines changed

docs/user/tutorial/index.rst

+1-2
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,7 @@ and it contains the following files:
5050

5151
``docs/``
5252
Directory holding all the Sphinx documentation sources,
53-
including some required dependencies in ``docs/requirements.txt``,
54-
the Sphinx configuration ``docs/source/conf.py``,
53+
including the Sphinx configuration ``docs/source/conf.py``
5554
and the root document ``docs/source/index.rst`` written in reStructuredText.
5655

5756
.. figure:: /_static/images/tutorial/github-template.png

readthedocs/doc_builder/backends/sphinx.py

+12
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,18 @@ def __init__(self, *args, **kwargs):
6161
self.config_file,
6262
)
6363
except ProjectConfigurationError:
64+
# NOTE: this exception handling here is weird to me.
65+
# We are raising this exception from inside `project.confi_file` when:
66+
# - the repository has multiple config files (none of them with `doc` in its filename)
67+
# - there is no config file at all
68+
#
69+
# IMO, if there are multiple config files,
70+
# the build should fail immediately communicating this to the user.
71+
# This can be achived by unhandle the exception here
72+
# and leaving `on_failure` Celery handle to deal with it.
73+
#
74+
# In case there is no config file, we should continue the build
75+
# because Read the Docs will automatically create one for it.
6476
pass
6577

6678
def _write_config(self, master_doc='index'):

readthedocs/rtd_tests/tests/test_middleware.py

+37-17
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
from unittest import mock
22

3-
from corsheaders.middleware import CorsMiddleware
3+
from corsheaders.middleware import (
4+
ACCESS_CONTROL_ALLOW_CREDENTIALS,
5+
ACCESS_CONTROL_ALLOW_ORIGIN,
6+
CorsMiddleware,
7+
)
48
from django.conf import settings
59
from django.http import HttpResponse
610
from django.test import TestCase, override_settings
@@ -73,7 +77,8 @@ def test_allow_linked_domain_from_public_version(self):
7377
HTTP_ORIGIN='http://my.valid.domain',
7478
)
7579
resp = self.middleware.process_response(request, {})
76-
self.assertIn('Access-Control-Allow-Origin', resp)
80+
self.assertIn(ACCESS_CONTROL_ALLOW_ORIGIN, resp)
81+
self.assertNotIn(ACCESS_CONTROL_ALLOW_CREDENTIALS, resp)
7782

7883
def test_dont_allow_linked_domain_from_private_version(self):
7984
self.version.privacy_level = PRIVATE
@@ -84,7 +89,8 @@ def test_dont_allow_linked_domain_from_private_version(self):
8489
HTTP_ORIGIN='http://my.valid.domain',
8590
)
8691
resp = self.middleware.process_response(request, {})
87-
self.assertNotIn('Access-Control-Allow-Origin', resp)
92+
self.assertNotIn(ACCESS_CONTROL_ALLOW_ORIGIN, resp)
93+
self.assertNotIn(ACCESS_CONTROL_ALLOW_CREDENTIALS, resp)
8894

8995
def test_allowed_api_public_version_from_another_domain(self):
9096
request = self.factory.get(
@@ -93,15 +99,17 @@ def test_allowed_api_public_version_from_another_domain(self):
9399
HTTP_ORIGIN='http://docs.another.domain',
94100
)
95101
resp = self.middleware.process_response(request, {})
96-
self.assertIn('Access-Control-Allow-Origin', resp)
102+
self.assertIn(ACCESS_CONTROL_ALLOW_ORIGIN, resp)
103+
self.assertNotIn(ACCESS_CONTROL_ALLOW_CREDENTIALS, resp)
97104

98105
request = self.factory.get(
99106
self.url,
100107
{'project': self.project.slug, 'version': self.version.slug},
101108
HTTP_ORIGIN='http://another.valid.domain',
102109
)
103110
resp = self.middleware.process_response(request, {})
104-
self.assertIn('Access-Control-Allow-Origin', resp)
111+
self.assertIn(ACCESS_CONTROL_ALLOW_ORIGIN, resp)
112+
self.assertNotIn(ACCESS_CONTROL_ALLOW_CREDENTIALS, resp)
105113

106114
def test_not_allowed_api_private_version_from_another_domain(self):
107115
self.version.privacy_level = PRIVATE
@@ -112,15 +120,17 @@ def test_not_allowed_api_private_version_from_another_domain(self):
112120
HTTP_ORIGIN='http://docs.another.domain',
113121
)
114122
resp = self.middleware.process_response(request, {})
115-
self.assertNotIn('Access-Control-Allow-Origin', resp)
123+
self.assertNotIn(ACCESS_CONTROL_ALLOW_ORIGIN, resp)
124+
self.assertNotIn(ACCESS_CONTROL_ALLOW_CREDENTIALS, resp)
116125

117126
request = self.factory.get(
118127
self.url,
119128
{'project': self.project.slug, 'version': self.version.slug},
120129
HTTP_ORIGIN='http://another.valid.domain',
121130
)
122131
resp = self.middleware.process_response(request, {})
123-
self.assertNotIn('Access-Control-Allow-Origin', resp)
132+
self.assertNotIn(ACCESS_CONTROL_ALLOW_ORIGIN, resp)
133+
self.assertNotIn(ACCESS_CONTROL_ALLOW_CREDENTIALS, resp)
124134

125135
def test_valid_subproject(self):
126136
self.assertTrue(
@@ -135,7 +145,8 @@ def test_valid_subproject(self):
135145
HTTP_ORIGIN='http://my.valid.domain',
136146
)
137147
resp = self.middleware.process_response(request, {})
138-
self.assertIn('Access-Control-Allow-Origin', resp)
148+
self.assertIn(ACCESS_CONTROL_ALLOW_ORIGIN, resp)
149+
self.assertNotIn(ACCESS_CONTROL_ALLOW_CREDENTIALS, resp)
139150

140151
def test_embed_api_private_version_linked_domain(self):
141152
self.version.privacy_level = PRIVATE
@@ -146,7 +157,8 @@ def test_embed_api_private_version_linked_domain(self):
146157
HTTP_ORIGIN='http://my.valid.domain',
147158
)
148159
resp = self.middleware.process_response(request, {})
149-
self.assertNotIn('Access-Control-Allow-Origin', resp)
160+
self.assertNotIn(ACCESS_CONTROL_ALLOW_ORIGIN, resp)
161+
self.assertNotIn(ACCESS_CONTROL_ALLOW_CREDENTIALS, resp)
150162

151163
def test_embed_api_external_url(self):
152164
request = self.factory.get(
@@ -174,15 +186,17 @@ def test_sustainability_endpoint_allways_allowed(self, has_donate_app):
174186
HTTP_ORIGIN='http://invalid.domain',
175187
)
176188
resp = self.middleware.process_response(request, {})
177-
self.assertIn('Access-Control-Allow-Origin', resp)
189+
self.assertIn(ACCESS_CONTROL_ALLOW_ORIGIN, resp)
190+
self.assertNotIn(ACCESS_CONTROL_ALLOW_CREDENTIALS, resp)
178191

179192
request = self.factory.get(
180193
'/api/v2/sustainability/',
181194
{'project': self.project.slug, 'active': True, 'version': self.version.slug},
182195
HTTP_ORIGIN='http://my.valid.domain',
183196
)
184197
resp = self.middleware.process_response(request, {})
185-
self.assertIn('Access-Control-Allow-Origin', resp)
198+
self.assertIn(ACCESS_CONTROL_ALLOW_ORIGIN, resp)
199+
self.assertNotIn(ACCESS_CONTROL_ALLOW_CREDENTIALS, resp)
186200

187201
@mock.patch('readthedocs.core.signals._has_donate_app')
188202
def test_sustainability_endpoint_no_ext(self, has_donate_app):
@@ -193,15 +207,17 @@ def test_sustainability_endpoint_no_ext(self, has_donate_app):
193207
HTTP_ORIGIN='http://invalid.domain',
194208
)
195209
resp = self.middleware.process_response(request, {})
196-
self.assertNotIn('Access-Control-Allow-Origin', resp)
210+
self.assertNotIn(ACCESS_CONTROL_ALLOW_ORIGIN, resp)
211+
self.assertNotIn(ACCESS_CONTROL_ALLOW_CREDENTIALS, resp)
197212

198213
request = self.factory.get(
199214
'/api/v2/sustainability/',
200215
{'project': self.project.slug, 'active': True, 'version': self.version.slug},
201216
HTTP_ORIGIN='http://my.valid.domain',
202217
)
203218
resp = self.middleware.process_response(request, {})
204-
self.assertNotIn('Access-Control-Allow-Origin', resp)
219+
self.assertNotIn(ACCESS_CONTROL_ALLOW_ORIGIN, resp)
220+
self.assertNotIn(ACCESS_CONTROL_ALLOW_CREDENTIALS, resp)
205221

206222
def test_apiv2_endpoint_not_allowed(self):
207223
request = self.factory.get(
@@ -210,7 +226,8 @@ def test_apiv2_endpoint_not_allowed(self):
210226
HTTP_ORIGIN='http://invalid.domain',
211227
)
212228
resp = self.middleware.process_response(request, {})
213-
self.assertNotIn('Access-Control-Allow-Origin', resp)
229+
self.assertNotIn(ACCESS_CONTROL_ALLOW_ORIGIN, resp)
230+
self.assertNotIn(ACCESS_CONTROL_ALLOW_CREDENTIALS, resp)
214231

215232
# This also doesn't work on registered domains.
216233
request = self.factory.get(
@@ -219,7 +236,8 @@ def test_apiv2_endpoint_not_allowed(self):
219236
HTTP_ORIGIN='http://my.valid.domain',
220237
)
221238
resp = self.middleware.process_response(request, {})
222-
self.assertNotIn('Access-Control-Allow-Origin', resp)
239+
self.assertNotIn(ACCESS_CONTROL_ALLOW_ORIGIN, resp)
240+
self.assertNotIn(ACCESS_CONTROL_ALLOW_CREDENTIALS, resp)
223241

224242
# Or from our public domain.
225243
request = self.factory.get(
@@ -228,7 +246,8 @@ def test_apiv2_endpoint_not_allowed(self):
228246
HTTP_ORIGIN='http://docs.readthedocs.io/',
229247
)
230248
resp = self.middleware.process_response(request, {})
231-
self.assertNotIn('Access-Control-Allow-Origin', resp)
249+
self.assertNotIn(ACCESS_CONTROL_ALLOW_ORIGIN, resp)
250+
self.assertNotIn(ACCESS_CONTROL_ALLOW_CREDENTIALS, resp)
232251

233252
# POST is not allowed
234253
request = self.factory.post(
@@ -237,7 +256,8 @@ def test_apiv2_endpoint_not_allowed(self):
237256
HTTP_ORIGIN='http://my.valid.domain',
238257
)
239258
resp = self.middleware.process_response(request, {})
240-
self.assertNotIn('Access-Control-Allow-Origin', resp)
259+
self.assertNotIn(ACCESS_CONTROL_ALLOW_ORIGIN, resp)
260+
self.assertNotIn(ACCESS_CONTROL_ALLOW_CREDENTIALS, resp)
241261

242262

243263
class TestSessionMiddleware(TestCase):

readthedocs/settings/base.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -728,8 +728,11 @@ def DOCKER_LIMITS(self):
728728
}
729729

730730
# CORS
731-
# So cookies can be included in cross-domain requests where needed (eg. sustainability API).
732-
CORS_ALLOW_CREDENTIALS = True
731+
# Don't allow sending cookies in cross-domain requests, this is so we can
732+
# relax our CORS headers for more views, but at the same time not opening
733+
# users to CSRF attacks. The sustainability API is the only view that requires
734+
# cookies to be send cross-site, we override that for that view only.
735+
CORS_ALLOW_CREDENTIALS = False
733736
CORS_ALLOW_HEADERS = (
734737
'x-requested-with',
735738
'content-type',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{% extends "core/email/common.html" %}
2+
3+
{% block content %}
4+
<p>
5+
You are receiving this email because you or someone else tried to signup for
6+
an account using email address: {{ email}}.
7+
</p>
8+
9+
<p>
10+
However, an account using that email address already exists. In case you have
11+
forgotten about this, please use the password forgotten procedure to recover
12+
your account:
13+
</p>
14+
15+
<p>
16+
<a href="{{ password_reset_url }}">{{ password_reset_url }}</a>
17+
</p>
18+
19+
<p>
20+
If you did not sign up for an account with Read the Docs, you can disregard this email.
21+
</p>
22+
23+
{% endblock %}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{% extends "core/email/common.txt" %}
2+
3+
{% block content %}
4+
5+
You are receiving this email because you or someone else tried to signup for
6+
an account using email address:
7+
8+
{{ email }}
9+
10+
However, an account using that email address already exists. In case you have
11+
forgotten about this, please use the password forgotten procedure to recover
12+
your account:
13+
14+
{{ password_reset_url }
15+
16+
If you did not sign up for an account with Read the Docs, you can disregard this email.
17+
{% endblock %}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Account Already Exists
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{% extends "core/email/common.html" %}
2+
3+
{% block content %}
4+
<p>
5+
You are receiving this email because you or someone else has requested a
6+
password for your user account. However, we do not have any record of a user
7+
with email {{ email }} in our database.
8+
</p>
9+
10+
<p>
11+
This mail can be safely ignored if you did not request a password reset.
12+
</p>
13+
14+
<p>
15+
If it was you, you can sign up for an account using the link below.
16+
</p>
17+
18+
<p>
19+
<a href="{{ signup_url }}">{{ signup_url }}</a>
20+
</p>
21+
{% endblock %}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{% extends "core/email/common.txt" %}
2+
3+
{% block content %}
4+
You are receiving this email because you or someone else has requested a
5+
password for your user account. However, we do not have any record of a user
6+
with email {{ email }} in our database.
7+
8+
This mail can be safely ignored if you did not request a password reset.
9+
10+
If it was you, you can sign up for an account using the link below.
11+
12+
{{ signup_url }}
13+
{% endblock %}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Password Reset Email

0 commit comments

Comments
 (0)