mirror of https://github.com/watcha-fr/synapse
Use Pydantic to systematically validate a first batch of endpoints in `synapse.rest.client.account`. (#13188)
parent
73c83c6411
commit
d642ce4b32
@ -0,0 +1 @@ |
||||
Improve validation of request bodies for the following client-server API endpoints: [`/account/password`](https://spec.matrix.org/v1.3/client-server-api/#post_matrixclientv3accountpassword), [`/account/password/email/requestToken`](https://spec.matrix.org/v1.3/client-server-api/#post_matrixclientv3accountpasswordemailrequesttoken), [`/account/deactivate`](https://spec.matrix.org/v1.3/client-server-api/#post_matrixclientv3accountdeactivate) and [`/account/3pid/email/requestToken`](https://spec.matrix.org/v1.3/client-server-api/#post_matrixclientv3account3pidemailrequesttoken). |
@ -0,0 +1,69 @@ |
||||
# Copyright 2022 The Matrix.org Foundation C.I.C. |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
# |
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
from typing import TYPE_CHECKING, Dict, Optional |
||||
|
||||
from pydantic import Extra, StrictInt, StrictStr, constr, validator |
||||
|
||||
from synapse.rest.models import RequestBodyModel |
||||
from synapse.util.threepids import validate_email |
||||
|
||||
|
||||
class AuthenticationData(RequestBodyModel): |
||||
""" |
||||
Data used during user-interactive authentication. |
||||
|
||||
(The name "Authentication Data" is taken directly from the spec.) |
||||
|
||||
Additional keys will be present, depending on the `type` field. Use `.dict()` to |
||||
access them. |
||||
""" |
||||
|
||||
class Config: |
||||
extra = Extra.allow |
||||
|
||||
session: Optional[StrictStr] = None |
||||
type: Optional[StrictStr] = None |
||||
|
||||
|
||||
class EmailRequestTokenBody(RequestBodyModel): |
||||
if TYPE_CHECKING: |
||||
client_secret: StrictStr |
||||
else: |
||||
# See also assert_valid_client_secret() |
||||
client_secret: constr( |
||||
regex="[0-9a-zA-Z.=_-]", # noqa: F722 |
||||
min_length=0, |
||||
max_length=255, |
||||
strict=True, |
||||
) |
||||
email: StrictStr |
||||
id_server: Optional[StrictStr] |
||||
id_access_token: Optional[StrictStr] |
||||
next_link: Optional[StrictStr] |
||||
send_attempt: StrictInt |
||||
|
||||
@validator("id_access_token", always=True) |
||||
def token_required_for_identity_server( |
||||
cls, token: Optional[str], values: Dict[str, object] |
||||
) -> Optional[str]: |
||||
if values.get("id_server") is not None and token is None: |
||||
raise ValueError("id_access_token is required if an id_server is supplied.") |
||||
return token |
||||
|
||||
# Canonicalise the email address. The addresses are all stored canonicalised |
||||
# in the database. This allows the user to reset his password without having to |
||||
# know the exact spelling (eg. upper and lower case) of address in the database. |
||||
# Without this, an email stored in the database as "foo@bar.com" would cause |
||||
# user requests for "FOO@bar.com" to raise a Not Found error. |
||||
_email_validator = validator("email", allow_reuse=True)(validate_email) |
@ -0,0 +1,23 @@ |
||||
from pydantic import BaseModel, Extra |
||||
|
||||
|
||||
class RequestBodyModel(BaseModel): |
||||
"""A custom version of Pydantic's BaseModel which |
||||
|
||||
- ignores unknown fields and |
||||
- does not allow fields to be overwritten after construction, |
||||
|
||||
but otherwise uses Pydantic's default behaviour. |
||||
|
||||
Ignoring unknown fields is a useful default. It means that clients can provide |
||||
unstable field not known to the server without the request being refused outright. |
||||
|
||||
Subclassing in this way is recommended by |
||||
https://pydantic-docs.helpmanual.io/usage/model_config/#change-behaviour-globally |
||||
""" |
||||
|
||||
class Config: |
||||
# By default, ignore fields that we don't recognise. |
||||
extra = Extra.ignore |
||||
# By default, don't allow fields to be reassigned after parsing. |
||||
allow_mutation = False |
@ -0,0 +1,53 @@ |
||||
# Copyright 2022 The Matrix.org Foundation C.I.C. |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
# |
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
import unittest |
||||
|
||||
from pydantic import ValidationError |
||||
|
||||
from synapse.rest.client.models import EmailRequestTokenBody |
||||
|
||||
|
||||
class EmailRequestTokenBodyTestCase(unittest.TestCase): |
||||
base_request = { |
||||
"client_secret": "hunter2", |
||||
"email": "alice@wonderland.com", |
||||
"send_attempt": 1, |
||||
} |
||||
|
||||
def test_token_required_if_id_server_provided(self) -> None: |
||||
with self.assertRaises(ValidationError): |
||||
EmailRequestTokenBody.parse_obj( |
||||
{ |
||||
**self.base_request, |
||||
"id_server": "identity.wonderland.com", |
||||
} |
||||
) |
||||
with self.assertRaises(ValidationError): |
||||
EmailRequestTokenBody.parse_obj( |
||||
{ |
||||
**self.base_request, |
||||
"id_server": "identity.wonderland.com", |
||||
"id_access_token": None, |
||||
} |
||||
) |
||||
|
||||
def test_token_typechecked_when_id_server_provided(self) -> None: |
||||
with self.assertRaises(ValidationError): |
||||
EmailRequestTokenBody.parse_obj( |
||||
{ |
||||
**self.base_request, |
||||
"id_server": "identity.wonderland.com", |
||||
"id_access_token": 1337, |
||||
} |
||||
) |
Loading…
Reference in new issue