Skip to content

Commit 539f1fa

Browse files
authored
Merge branch '6-dev' into install-reorg-1
2 parents 532e921 + a338ab1 commit 539f1fa

File tree

3 files changed

+188
-8
lines changed

3 files changed

+188
-8
lines changed

docs/backend/behaviors.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,10 @@ If we were to represent the behavior of choosing the correct outlet adapter in P
332332

333333
This process of choosing the right adapter based on the information of the context and the requested interface implements the design pattern of an abstract factory.
334334

335+
```{hint} The notation `ISchuko(context)` is a shortcut for `getAdapter(context, ISchuko)`.
336+
It executes exactly the same logic behind the scenes with the same result.
337+
```
338+
335339
Similarly, using the {ref}`behavior code example <behavior-code-example>` above:
336340
337341
- You would call an abstract factory with `getAdapter(context, IPriceBehavior)` to get an adapter, `price_for_context`.

docs/classic-ui/csrf.md

Lines changed: 173 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,182 @@
11
---
22
myst:
33
html_meta:
4-
"description": ""
5-
"property=og:description": ""
6-
"property=og:title": ""
7-
"keywords": ""
4+
"description": "How to protect Plone against CSRF attacks."
5+
"property=og:description": "How to protect Plone against CSRF attacks."
6+
"property=og:title": "Cross-Site Request Forgery protection in Plone"
7+
"keywords": "CSRF, security, token, protection, Cross-Site Request Forgery"
88
---
99

1010
(classic-ui-csrf-label)=
1111

1212
# Cross-Site Request Forgery (CSRF)
1313

14+
Cross-Site Request Forgery (CSRF or XSRF) is a type of web attack that allows an attacker to send malicious requests to a web application on behalf of a legitimate user.
15+
The attack works by tricking the user's web browser into sending a request to the web application that the user did not intentionally make.
16+
This can allow an attacker to perform actions on the web application without the user's knowledge or consent.
17+
18+
For example, consider a web application that allows users to transfer money between accounts.
19+
An attacker could craft a malicious link or form that, when clicked or submitted by a victim, would transfer money from the victim's account to the attacker's account.
20+
If the victim is logged into the web application and clicks the link or form, the web application would receive a request to transfer the money, and it would comply with the request because it appears to come from a legitimate user.
21+
22+
To protect against CSRF attacks, Plone uses CSRF tokens to verify the authenticity of requests.
23+
CSRF tokens are unique, secret values that are generated by the web application and included in forms and links.
24+
When a form or link with a valid CSRF token is submitted, the web application can verify the authenticity of the request by checking the token.
25+
If the token is missing or invalid, the request is rejected.
26+
27+
## Auto protection
28+
29+
In Plone, CSRF protection is done almost transparently by [`plone.protect`](https://pypi.org/project/plone.protect/).
30+
One important aspect of `plone.protect` is that it performs the CSRF token validation at the database transaction commit time (at the end of the request), rather than at the beginning of the request.
31+
This means that the view can execute and make changes to the database, but the changes will not be persisted unless a valid CSRF token is present in the request.
32+
33+
When a logged-in user requests a page, Plone automatically includes the CSRF token in all forms by applying a transform (using `plone.transformchain`) that adds a hidden input with its value set to the token.
34+
This includes, but is not limited to the following:
35+
36+
- add and edit forms
37+
- control panels
38+
- custom z3c forms
39+
40+
## Manual protection
41+
42+
To ensure that code that is not part of a database transaction—such as code that writes to an external API or a service that is not automatically included in the transaction mechanism—is protected, you will need to manually implement protection for that code.
43+
44+
`plone.protect` offers the `@protect` decorator.
45+
The decorator expects a callable to perform the check.
46+
There are two checks implemented in `plone.protect`:
47+
48+
### CSRF token check with `CheckAuthenticator`
49+
50+
Checks whether a valid CSRF token is present in the request and raises `Unauthorized` if not.
51+
52+
Usage example:
53+
54+
```python
55+
from plone.protect import CheckAuthenticator
56+
from plone.protect import protect
57+
58+
@protect(CheckAuthenticator)
59+
def write_to_api_or_service(self):
60+
# code here
61+
...
62+
```
63+
64+
### HTTP POST check with `PostOnly`
65+
66+
Checks whether the request is an HTTP POST request, and raises `Unauthorized` if not.
67+
This helps to mitigate clicks on malicious links.
68+
69+
Usage example:
70+
71+
```python
72+
from plone.protect import PostOnly
73+
from plone.protect import protect
74+
75+
@protect(PostOnly)
76+
def write_to_api_or_service(self):
77+
# code here
78+
...
79+
```
80+
81+
## How to add a CSRF token to a link or form
82+
83+
To pass a CSRF token you need either to:
84+
85+
- pass an HTTP GET parameter name `_authenticator` with the token as the value,
86+
- include a form field named `_authenticator` with the token as the value and submit it with the form, or
87+
- add an HTTP header named `X-CSRF-TOKEN` with the token as the value.
88+
89+
To add a token as an HTTP GET parameter to a link in a template, you can utilize the authenticator view:
90+
91+
```html
92+
<tal:authenticator tal:define="token context/@@authenticator/token">
93+
<a href="${python:context.absolute_url()}/myprotected_view?_authenticator=${token}" >Link to some view</a>
94+
</tal:authenticator>
95+
```
96+
97+
To add a hidden field with a token to a form in a template, the above view can be used as follows:
98+
99+
```html
100+
<span tal:replace="structure context/@@authenticator/authenticator"/>
101+
```
102+
103+
In Python code, a helper function can be used:
104+
105+
```python
106+
from plone.protect.authenticator import createToken
107+
108+
token = createToken()
109+
```
110+
111+
To add an authenticator token to an existing URL with query parameters:
112+
113+
```python
114+
from plone.protect.authenticator import createToken
115+
from urllib.parse import urlencode
116+
from urllib.parse import urlparse
117+
from urllib.parse import urlunparse
118+
119+
# The existing URL that you want to add a query parameter to
120+
url = f"https://www.example.com?param1=value1"
121+
122+
# Parse the URL into its component parts
123+
parsed_url = urlparse(url)
124+
125+
# Add the new query parameters to the 'query' component of the URL
126+
token_query = urlencode({"_authenticator": createToken()})
127+
new_query = f"{parsed_url.query}&{token_query}"
128+
129+
# Reassemble the URL with the updated query string
130+
final_url = urlunparse(
131+
(
132+
parsed_url.scheme,
133+
parsed_url.netloc,
134+
parsed_url.path,
135+
parsed_url.params,
136+
new_query,
137+
parsed_url.fragment)
138+
)
139+
```
140+
141+
142+
## How to allow writes in absence of a protecting token
143+
144+
To allow certain objects to be modified and written to the database without protection, follow these steps:
145+
146+
1. Identify the modified object as a single object in the database.
147+
2. If an attribute of the object is a "persistent" attribute (for example, a `PersistentDict` or `PersistentList` instance, a `BTree`, or an `annotation`), use this instead.
148+
3. Use the `safeWrite` function to mark the object as safe for writing.
149+
150+
```{note}
151+
This is the preferred method for allowing modification and writing of specific objects to the database.
152+
```
153+
154+
```python
155+
from plone.protect.utils import safeWrite
156+
157+
def some_function(obj, request):
158+
safeWrite(obj, request)
159+
obj.foo = "bar" # modify obj
160+
```
161+
162+
If there are lots of modifications or it is not possible to identify the boundaries of the writes, the protection can be disabled for the whole current request.
163+
Then the request can be marked with the `IDisableCSRFProtection` marker interface.
164+
165+
```python
166+
from plone.protect.interfaces import IDisableCSRFProtection
167+
from zope.interface import alsoProvides
168+
169+
def some_function(request):
170+
alsoProvides(request, IDisableCSRFProtection)
171+
# modify the database here
172+
```
173+
174+
Disabling all CSRF protection for the whole Plone instance is possible by starting Plone with the environment variable `PLONE_CSRF_DISABLED=true` set.
175+
This is not recommended but can be handy temporarily in special situations.
176+
177+
178+
## Further reading
179+
180+
```{seealso}
181+
The [README file of `plone.protect`](https://github.com/plone/plone.protect/blob/master/README.rst) explains the usage and also validation in detail.
182+
```

docs/glossary.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,13 @@ cookiecutter-plone-starter
3434
cookiecutter-zope-instance
3535
[cookiecutter-zope-instance](https://github.com/plone/cookiecutter-zope-instance) is a cookiecutter template to create a full and complex configuration of a Zope WSGI instance.
3636
37+
CSRF
38+
Cross-Site Request Forgery
39+
Cross-Site Request Forgery (CSRF or XSRF) is a type of web attack that allows an attacker to send malicious requests to a web application on behalf of a legitimate user.
40+
The attack works by tricking the user's web browser into sending a request to the web application that the user did not intentionally make.
41+
This can allow an attacker to perform actions on the web application without the user's knowledge or consent.
42+
In Plone, CSRF protection is done almost transparently by [`plone.protect`](https://pypi.org/project/plone.protect/).
43+
3744
CSS
3845
Cascading Style Sheets (CSS) is a stylesheet language used for describing the (most of the times visual) representation of web pages.
3946
@@ -411,7 +418,7 @@ Internationalization
411418
Developers and template authors usually internationalize the application.
412419
"i18n" is shorthand for "internationalization" (the letter "I", 18 letters, the letter "N").
413420
Plone is fully internationalized.
414-
421+
415422
```{seealso}
416423
{term}`localization`
417424
```
@@ -436,7 +443,7 @@ language tag
436443
A language tag is a string used as an identifier for a language.
437444
A language tag may have one or more subtags.
438445
The basic form of a language tag is `LANGUAGE-[SUBTAG]`.
439-
446+
440447
```{seealso}
441448
- W3C article [Language tags in HTML and XML](https://www.w3.org/International/articles/language-tags/)
442449
- W3C Working Draft [Language Tags and Locale Identifiers for the World Wide Web](https://www.w3.org/TR/ltli/)
@@ -480,7 +487,7 @@ react-intl
480487
A library that is part of [Format.JS](https://formatjs.io/docs/getting-started/installation) which helps developers set up their applications for internationalization.
481488
482489
WSGI
483-
The Web Server Gateway Interface (WSGI, pronounced _WIZ-ghee_) is a simple calling convention for web servers to forward requests to web applications or frameworks written in the Python programming language.
490+
The Web Server Gateway Interface (WSGI, pronounced _WIZ-ghee_) is a simple calling convention for web servers to forward requests to web applications or frameworks written in the Python programming language.
484491
485492
ZEO
486493
[ZEO](https://zeo.readthedocs.io/en/latest/) is a client-server storage for ZODB for sharing a single storage among many clients.
@@ -565,7 +572,7 @@ Factory Type Information
565572
- Whether discussion is enabled.
566573
- Providing the `factory_type_information` dictionary.
567574
This is used elsewhere in the code (often in `__init__.py` of a product) to set the initial values for a ZODB Factory Type Information object (an object in the `portal_types` tool).
568-
575+
569576
```{seealso}
570577
[`FactoryTypeInformation` class source code](https://github.com/zopefoundation/Products.CMFCore/blob/361a30e0c72a15a21f88433b8d5fc49331f36728/src/Products/CMFCore/TypesTool.py#L431)
571578
```

0 commit comments

Comments
 (0)