mirror of
https://github.com/element-hq/synapse.git
synced 2025-12-05 01:10:13 +00:00
Compare commits
34 Commits
release-v1
...
madlittlem
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c50223e50a | ||
|
|
62945266a8 | ||
|
|
ebc21a8c67 | ||
|
|
e5a53819fc | ||
|
|
66b24d3d00 | ||
|
|
2b59e738ee | ||
|
|
b1d030a107 | ||
|
|
7c2284b2f2 | ||
|
|
d69c00b5a1 | ||
|
|
2d23250da7 | ||
|
|
234d07eb09 | ||
|
|
bd9a1079bc | ||
|
|
3eb92369ca | ||
|
|
09f377fa52 | ||
|
|
f1b0f9a4ef | ||
|
|
f1ecf46647 | ||
|
|
57bf44941e | ||
|
|
3d60a58ad6 | ||
|
|
8208186e3c | ||
|
|
29d586311d | ||
|
|
512c9efcb3 | ||
|
|
35c361c0d9 | ||
|
|
95853c5f31 | ||
|
|
eb019c03c4 | ||
|
|
eedab12e6d | ||
|
|
483602efb2 | ||
|
|
5da7081197 | ||
|
|
5cf74c2da0 | ||
|
|
adce8a0111 | ||
|
|
790ce14e46 | ||
|
|
ecbc0b740c | ||
|
|
0db5d247f8 | ||
|
|
02d09e3f0c | ||
|
|
b90ad26ebc |
@@ -60,7 +60,7 @@ trial_postgres_tests = [
|
||||
{
|
||||
"python-version": "3.9",
|
||||
"database": "postgres",
|
||||
"postgres-version": "11",
|
||||
"postgres-version": "13",
|
||||
"extras": "all",
|
||||
}
|
||||
]
|
||||
|
||||
2
.github/workflows/docs-pr-netlify.yaml
vendored
2
.github/workflows/docs-pr-netlify.yaml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
# There's a 'download artifact' action, but it hasn't been updated for the workflow_run action
|
||||
# (https://github.com/actions/download-artifact/issues/60) so instead we get this mess:
|
||||
- name: 📥 Download artifact
|
||||
uses: dawidd6/action-download-artifact@bf251b5aa9c2f7eeb574a96ee720e24f801b7c11 # v6
|
||||
uses: dawidd6/action-download-artifact@80620a5d27ce0ae443b965134db88467fc607b43 # v7
|
||||
with:
|
||||
workflow: docs-pr.yaml
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
|
||||
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -581,7 +581,7 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- python-version: "3.9"
|
||||
postgres-version: "11"
|
||||
postgres-version: "13"
|
||||
|
||||
- python-version: "3.13"
|
||||
postgres-version: "17"
|
||||
|
||||
40
Cargo.lock
generated
40
Cargo.lock
generated
@@ -13,9 +13,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.93"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
|
||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
@@ -168,9 +168,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
|
||||
checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
@@ -272,9 +272,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pyo3"
|
||||
version = "0.23.2"
|
||||
version = "0.23.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f54b3d09cbdd1f8c20650b28e7b09e338881482f4aa908a5f61a00c98fba2690"
|
||||
checksum = "e484fd2c8b4cb67ab05a318f1fd6fa8f199fcc30819f08f07d200809dba26c15"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cfg-if",
|
||||
@@ -291,9 +291,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-build-config"
|
||||
version = "0.23.2"
|
||||
version = "0.23.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3015cf985888fe66cfb63ce0e321c603706cd541b7aec7ddd35c281390af45d8"
|
||||
checksum = "dc0e0469a84f208e20044b98965e1561028180219e35352a2afaf2b942beff3b"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"target-lexicon",
|
||||
@@ -301,9 +301,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-ffi"
|
||||
version = "0.23.2"
|
||||
version = "0.23.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fca7cd8fd809b5ac4eefb89c1f98f7a7651d3739dfb341ca6980090f554c270"
|
||||
checksum = "eb1547a7f9966f6f1a0f0227564a9945fe36b90da5a93b3933fc3dc03fae372d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pyo3-build-config",
|
||||
@@ -322,9 +322,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros"
|
||||
version = "0.23.2"
|
||||
version = "0.23.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34e657fa5379a79151b6ff5328d9216a84f55dc93b17b08e7c3609a969b73aa0"
|
||||
checksum = "fdb6da8ec6fa5cedd1626c886fc8749bdcbb09424a86461eb8cdf096b7c33257"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"pyo3-macros-backend",
|
||||
@@ -334,9 +334,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros-backend"
|
||||
version = "0.23.2"
|
||||
version = "0.23.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "295548d5ffd95fd1981d2d3cf4458831b21d60af046b729b6fd143b0ba7aee2f"
|
||||
checksum = "38a385202ff5a92791168b1136afae5059d3ac118457bb7bc304c197c2d33e7d"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@@ -431,18 +431,18 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.215"
|
||||
version = "1.0.216"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
|
||||
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.215"
|
||||
version = "1.0.216"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
|
||||
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -451,9 +451,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.133"
|
||||
version = "1.0.134"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
|
||||
checksum = "d00f4175c42ee48b15416f6193a959ba3a0d67fc699a0db9ad12df9f83991c7d"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
|
||||
1
changelog.d/17846.misc
Normal file
1
changelog.d/17846.misc
Normal file
@@ -0,0 +1 @@
|
||||
Update Alpine Linux Synapse Package Maintainer within installation.md.
|
||||
1
changelog.d/17849.feature
Normal file
1
changelog.d/17849.feature
Normal file
@@ -0,0 +1 @@
|
||||
Added the `email.tlsname` config option. This allows specifying the domain name used to validate the SMTP server's TLS certificate separately from the `email.smtp_host` to connect to.
|
||||
1
changelog.d/17916.feature
Normal file
1
changelog.d/17916.feature
Normal file
@@ -0,0 +1 @@
|
||||
Module developers will have access to user id of requester when adding `check_username_for_spam` callbacks to `spam_checker_module_callbacks`. Contributed by Wilson@Pangea.chat.
|
||||
1
changelog.d/17930.bugfix
Normal file
1
changelog.d/17930.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Fix bug when rejecting withdrew invite with a third_party_rules module, where the invite would be stuck for the client.
|
||||
3
changelog.d/17948.feature
Normal file
3
changelog.d/17948.feature
Normal file
@@ -0,0 +1,3 @@
|
||||
Add endpoints to Admin API to fetch the number of invites the provided user has sent after a given timestamp,
|
||||
fetch the number of rooms the provided user has joined after a given timestamp, and get report IDs of event
|
||||
reports against a provided user (ie where the user was the sender of the reported event).
|
||||
1
changelog.d/17954.doc
Normal file
1
changelog.d/17954.doc
Normal file
@@ -0,0 +1 @@
|
||||
Update `synapse.app.generic_worker` documentation to only recommend `GET` requests for stream writer routes by default, unless the worker is also configured as a stream writer. Contributed by @evoL.
|
||||
1
changelog.d/17964.feature
Normal file
1
changelog.d/17964.feature
Normal file
@@ -0,0 +1 @@
|
||||
Support stable account suspension from [MSC3823](https://github.com/matrix-org/matrix-spec-proposals/pull/3823).
|
||||
1
changelog.d/17976.doc
Normal file
1
changelog.d/17976.doc
Normal file
@@ -0,0 +1 @@
|
||||
Add previously-undocumented `last_seen_ts` to query user admin API.
|
||||
1
changelog.d/17983.feature
Normal file
1
changelog.d/17983.feature
Normal file
@@ -0,0 +1 @@
|
||||
Add `macaroon_secret_key_path` config option.
|
||||
1
changelog.d/17992.doc
Normal file
1
changelog.d/17992.doc
Normal file
@@ -0,0 +1 @@
|
||||
Improve documentation for the `TaskScheduler` class.
|
||||
1
changelog.d/17994.doc
Normal file
1
changelog.d/17994.doc
Normal file
@@ -0,0 +1 @@
|
||||
Fix example in reverse proxy docs to include server port.
|
||||
1
changelog.d/17996.misc
Normal file
1
changelog.d/17996.misc
Normal file
@@ -0,0 +1 @@
|
||||
Add `RoomID` & `EventID` rust types.
|
||||
1
changelog.d/17998.misc
Normal file
1
changelog.d/17998.misc
Normal file
@@ -0,0 +1 @@
|
||||
Fix various type errors across the codebase.
|
||||
1
changelog.d/17999.misc
Normal file
1
changelog.d/17999.misc
Normal file
@@ -0,0 +1 @@
|
||||
Bump mypy from 1.11.2 to 1.12.1.
|
||||
1
changelog.d/18017.misc
Normal file
1
changelog.d/18017.misc
Normal file
@@ -0,0 +1 @@
|
||||
Disable DB statement timeout when doing a purge room since it can be quite long.
|
||||
1
changelog.d/18020.misc
Normal file
1
changelog.d/18020.misc
Normal file
@@ -0,0 +1 @@
|
||||
Remove some remaining uses of `twisted.internet.defer.returnValue`. Contributed by Colin Watson.
|
||||
1
changelog.d/18029.bugfix
Normal file
1
changelog.d/18029.bugfix
Normal file
@@ -0,0 +1 @@
|
||||
Fix a bug preventing the admin redaction endpoint from working on messages from remote users.
|
||||
1
changelog.d/18034.removal
Normal file
1
changelog.d/18034.removal
Normal file
@@ -0,0 +1 @@
|
||||
Remove support for PostgreSQL 11 and 12. Contributed by @clokep.
|
||||
1
changelog.d/18064.doc
Normal file
1
changelog.d/18064.doc
Normal file
@@ -0,0 +1 @@
|
||||
Document `tls` option for a worker instance in `instance_map`.
|
||||
@@ -245,7 +245,7 @@ class SynapseCmd(cmd.Cmd):
|
||||
|
||||
if "flows" not in json_res:
|
||||
print("Failed to find any login flows.")
|
||||
defer.returnValue(False)
|
||||
return False
|
||||
|
||||
flow = json_res["flows"][0] # assume first is the one we want.
|
||||
if "type" not in flow or "m.login.password" != flow["type"] or "stages" in flow:
|
||||
@@ -254,8 +254,8 @@ class SynapseCmd(cmd.Cmd):
|
||||
"Unable to login via the command line client. Please visit "
|
||||
"%s to login." % fallback_url
|
||||
)
|
||||
defer.returnValue(False)
|
||||
defer.returnValue(True)
|
||||
return False
|
||||
return True
|
||||
|
||||
def do_emailrequest(self, line):
|
||||
"""Requests the association of a third party identifier
|
||||
|
||||
@@ -78,7 +78,7 @@ class TwistedHttpClient(HttpClient):
|
||||
url, data, headers_dict={"Content-Type": ["application/json"]}
|
||||
)
|
||||
body = yield readBody(response)
|
||||
defer.returnValue((response.code, body))
|
||||
return response.code, body
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def get_json(self, url, args=None):
|
||||
@@ -88,7 +88,7 @@ class TwistedHttpClient(HttpClient):
|
||||
url = "%s?%s" % (url, qs)
|
||||
response = yield self._create_get_request(url)
|
||||
body = yield readBody(response)
|
||||
defer.returnValue(json.loads(body))
|
||||
return json.loads(body)
|
||||
|
||||
def _create_put_request(self, url, json_data, headers_dict: Optional[dict] = None):
|
||||
"""Wrapper of _create_request to issue a PUT request"""
|
||||
@@ -134,7 +134,7 @@ class TwistedHttpClient(HttpClient):
|
||||
response = yield self._create_request(method, url)
|
||||
|
||||
body = yield readBody(response)
|
||||
defer.returnValue(json.loads(body))
|
||||
return json.loads(body)
|
||||
|
||||
@defer.inlineCallbacks
|
||||
def _create_request(
|
||||
@@ -173,7 +173,7 @@ class TwistedHttpClient(HttpClient):
|
||||
if self.verbose:
|
||||
print("Status %s %s" % (response.code, response.phrase))
|
||||
print(pformat(list(response.headers.getAllRawHeaders())))
|
||||
defer.returnValue(response)
|
||||
return response
|
||||
|
||||
def sleep(self, seconds):
|
||||
d = defer.Deferred()
|
||||
|
||||
@@ -38,6 +38,9 @@ server {
|
||||
{% if using_unix_sockets %}
|
||||
proxy_pass http://unix:/run/main_public.sock;
|
||||
{% else %}
|
||||
# note: do not add a path (even a single /) after the port in `proxy_pass`,
|
||||
# otherwise nginx will canonicalise the URI and cause signature verification
|
||||
# errors.
|
||||
proxy_pass http://localhost:8080;
|
||||
{% endif %}
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
|
||||
@@ -60,10 +60,11 @@ paginate through.
|
||||
anything other than the return value of `next_token` from a previous call. Defaults to `0`.
|
||||
* `dir`: string - Direction of event report order. Whether to fetch the most recent
|
||||
first (`b`) or the oldest first (`f`). Defaults to `b`.
|
||||
* `user_id`: string - Is optional and filters to only return users with user IDs that
|
||||
contain this value. This is the user who reported the event and wrote the reason.
|
||||
* `room_id`: string - Is optional and filters to only return rooms with room IDs that
|
||||
contain this value.
|
||||
* `user_id`: optional string - Filter by the user ID of the reporter. This is the user who reported the event
|
||||
and wrote the reason.
|
||||
* `room_id`: optional string - Filter by room id.
|
||||
* `event_sender_user_id`: optional string - Filter by the sender of the reported event. This is the user who
|
||||
the report was made against.
|
||||
|
||||
**Response**
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ It returns a JSON body like the following:
|
||||
"erased": false,
|
||||
"shadow_banned": 0,
|
||||
"creation_ts": 1560432506,
|
||||
"last_seen_ts": 1732919539393,
|
||||
"appservice_id": null,
|
||||
"consent_server_notice_sent": null,
|
||||
"consent_version": null,
|
||||
@@ -477,9 +478,9 @@ with a body of:
|
||||
}
|
||||
```
|
||||
|
||||
## List room memberships of a user
|
||||
## List joined rooms of a user
|
||||
|
||||
Gets a list of all `room_id` that a specific `user_id` is member.
|
||||
Gets a list of all `room_id` that a specific `user_id` is joined to and is a member of (participating in).
|
||||
|
||||
The API is:
|
||||
|
||||
@@ -516,6 +517,73 @@ The following fields are returned in the JSON response body:
|
||||
- `joined_rooms` - An array of `room_id`.
|
||||
- `total` - Number of rooms.
|
||||
|
||||
## Get the number of invites sent by the user
|
||||
|
||||
Fetches the number of invites sent by the provided user ID across all rooms
|
||||
after the given timestamp.
|
||||
|
||||
```
|
||||
GET /_synapse/admin/v1/users/$user_id/sent_invite_count
|
||||
```
|
||||
|
||||
**Parameters**
|
||||
|
||||
The following parameters should be set in the URL:
|
||||
|
||||
* `user_id`: fully qualified: for example, `@user:server.com`
|
||||
|
||||
The following should be set as query parameters in the URL:
|
||||
|
||||
* `from_ts`: int, required. A timestamp in ms from the unix epoch. Only
|
||||
invites sent at or after the provided timestamp will be returned.
|
||||
This works by comparing the provided timestamp to the `received_ts`
|
||||
column in the `events` table.
|
||||
Note: https://currentmillis.com/ is a useful tool for converting dates
|
||||
into timestamps and vice versa.
|
||||
|
||||
A response body like the following is returned:
|
||||
|
||||
```json
|
||||
{
|
||||
"invite_count": 30
|
||||
}
|
||||
```
|
||||
|
||||
_Added in Synapse 1.122.0_
|
||||
|
||||
## Get the cumulative number of rooms a user has joined after a given timestamp
|
||||
|
||||
Fetches the number of rooms that the user joined after the given timestamp, even
|
||||
if they have subsequently left/been banned from those rooms.
|
||||
|
||||
```
|
||||
GET /_synapse/admin/v1/users/$<user_id/cumulative_joined_room_count
|
||||
```
|
||||
|
||||
**Parameters**
|
||||
|
||||
The following parameters should be set in the URL:
|
||||
|
||||
* `user_id`: fully qualified: for example, `@user:server.com`
|
||||
|
||||
The following should be set as query parameters in the URL:
|
||||
|
||||
* `from_ts`: int, required. A timestamp in ms from the unix epoch. Only
|
||||
invites sent at or after the provided timestamp will be returned.
|
||||
This works by comparing the provided timestamp to the `received_ts`
|
||||
column in the `events` table.
|
||||
Note: https://currentmillis.com/ is a useful tool for converting dates
|
||||
into timestamps and vice versa.
|
||||
|
||||
A response body like the following is returned:
|
||||
|
||||
```json
|
||||
{
|
||||
"cumulative_joined_room_count": 30
|
||||
}
|
||||
```
|
||||
_Added in Synapse 1.122.0_
|
||||
|
||||
## Account Data
|
||||
Gets information about account data for a specific `user_id`.
|
||||
|
||||
@@ -1444,4 +1512,6 @@ The following fields are returned in the JSON response body:
|
||||
- `failed_redactions` - dictionary - the keys of the dict are event ids the process was unable to redact, if any, and the values are
|
||||
the corresponding error that caused the redaction to fail
|
||||
|
||||
_Added in Synapse 1.116.0._
|
||||
_Added in Synapse 1.116.0._
|
||||
|
||||
|
||||
|
||||
@@ -245,7 +245,7 @@ this callback.
|
||||
_First introduced in Synapse v1.37.0_
|
||||
|
||||
```python
|
||||
async def check_username_for_spam(user_profile: synapse.module_api.UserProfile) -> bool
|
||||
async def check_username_for_spam(user_profile: synapse.module_api.UserProfile, requester_id: str) -> bool
|
||||
```
|
||||
|
||||
Called when computing search results in the user directory. The module must return a
|
||||
@@ -264,6 +264,8 @@ The profile is represented as a dictionary with the following keys:
|
||||
The module is given a copy of the original dictionary, so modifying it from within the
|
||||
module cannot modify a user's profile when included in user directory search results.
|
||||
|
||||
The requester_id parameter is the ID of the user that called the user directory API.
|
||||
|
||||
If multiple modules implement this callback, they will be considered in order. If a
|
||||
callback returns `False`, Synapse falls through to the next one. The value of the first
|
||||
callback that does not return `False` will be used. If this happens, Synapse will not call
|
||||
|
||||
@@ -74,7 +74,7 @@ server {
|
||||
proxy_pass http://localhost:8008;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header Host $host:$server_port;
|
||||
|
||||
# Nginx by default only allows file uploads up to 1M in size
|
||||
# Increase client_max_body_size to match max_upload_size defined in homeserver.yaml
|
||||
|
||||
@@ -157,7 +157,7 @@ sudo pip install py-bcrypt
|
||||
|
||||
#### Alpine Linux
|
||||
|
||||
6543 maintains [Synapse packages for Alpine Linux](https://pkgs.alpinelinux.org/packages?name=synapse&branch=edge) in the community repository. Install with:
|
||||
Jahway603 maintains [Synapse packages for Alpine Linux](https://pkgs.alpinelinux.org/packages?name=synapse&branch=edge) in the community repository. Install with:
|
||||
|
||||
```sh
|
||||
sudo apk add synapse
|
||||
|
||||
@@ -72,8 +72,8 @@ class ExampleSpamChecker:
|
||||
async def user_may_publish_room(self, userid, room_id):
|
||||
return True # allow publishing of all rooms
|
||||
|
||||
async def check_username_for_spam(self, user_profile):
|
||||
return False # allow all usernames
|
||||
async def check_username_for_spam(self, user_profile, requester_id):
|
||||
return False # allow all usernames regardless of requester
|
||||
|
||||
async def check_registration_for_spam(
|
||||
self,
|
||||
|
||||
@@ -117,6 +117,14 @@ each upgrade are complete before moving on to the next upgrade, to avoid
|
||||
stacking them up. You can monitor the currently running background updates with
|
||||
[the Admin API](usage/administration/admin_api/background_updates.html#status).
|
||||
|
||||
# Upgrading to v1.122.0
|
||||
|
||||
## Dropping support for PostgreSQL 11 and 12
|
||||
|
||||
In line with our [deprecation policy](deprecation_policy.md), we've dropped
|
||||
support for PostgreSQL 11 and 12, as they are no longer supported upstream.
|
||||
This release of Synapse requires PostgreSQL 13+.
|
||||
|
||||
# Upgrading to v1.120.0
|
||||
|
||||
## Removal of experimental MSC3886 feature
|
||||
|
||||
@@ -673,8 +673,9 @@ This setting has the following sub-options:
|
||||
TLS via STARTTLS *if the SMTP server supports it*. If this option is set,
|
||||
Synapse will refuse to connect unless the server supports STARTTLS.
|
||||
* `enable_tls`: By default, if the server supports TLS, it will be used, and the server
|
||||
must present a certificate that is valid for 'smtp_host'. If this option
|
||||
must present a certificate that is valid for `tlsname`. If this option
|
||||
is set to false, TLS will not be used.
|
||||
* `tlsname`: The domain name the SMTP server's TLS certificate must be valid for, defaulting to `smtp_host`.
|
||||
* `notif_from`: defines the "From" address to use when sending emails.
|
||||
It must be set if email sending is enabled. The placeholder '%(app)s' will be replaced by the application name,
|
||||
which is normally set in `app_name`, but may be overridden by the
|
||||
@@ -741,6 +742,7 @@ email:
|
||||
force_tls: true
|
||||
require_transport_security: true
|
||||
enable_tls: false
|
||||
tlsname: mail.server.example.com
|
||||
notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>"
|
||||
app_name: my_branded_matrix_server
|
||||
enable_notifs: true
|
||||
@@ -3091,6 +3093,22 @@ Example configuration:
|
||||
```yaml
|
||||
macaroon_secret_key: <PRIVATE STRING>
|
||||
```
|
||||
---
|
||||
### `macaroon_secret_key_path`
|
||||
|
||||
An alternative to [`macaroon_secret_key`](#macaroon_secret_key):
|
||||
allows the secret key to be specified in an external file.
|
||||
|
||||
The file should be a plain text file, containing only the secret key.
|
||||
Synapse reads the secret key from the given file once at startup.
|
||||
|
||||
Example configuration:
|
||||
```yaml
|
||||
macaroon_secret_key_path: /path/to/secrets/file
|
||||
```
|
||||
|
||||
_Added in Synapse 1.121.0._
|
||||
|
||||
---
|
||||
### `form_secret`
|
||||
|
||||
@@ -4447,6 +4465,10 @@ instance_map:
|
||||
worker1:
|
||||
host: localhost
|
||||
port: 8034
|
||||
other:
|
||||
host: localhost
|
||||
port: 8035
|
||||
tls: true
|
||||
```
|
||||
Example configuration(#2, for UNIX sockets):
|
||||
```yaml
|
||||
|
||||
@@ -273,17 +273,6 @@ information.
|
||||
^/_matrix/client/(api/v1|r0|v3|unstable)/knock/
|
||||
^/_matrix/client/(api/v1|r0|v3|unstable)/profile/
|
||||
|
||||
# Account data requests
|
||||
^/_matrix/client/(r0|v3|unstable)/.*/tags
|
||||
^/_matrix/client/(r0|v3|unstable)/.*/account_data
|
||||
|
||||
# Receipts requests
|
||||
^/_matrix/client/(r0|v3|unstable)/rooms/.*/receipt
|
||||
^/_matrix/client/(r0|v3|unstable)/rooms/.*/read_markers
|
||||
|
||||
# Presence requests
|
||||
^/_matrix/client/(api/v1|r0|v3|unstable)/presence/
|
||||
|
||||
# User directory search requests
|
||||
^/_matrix/client/(r0|v3|unstable)/user_directory/search$
|
||||
|
||||
@@ -292,6 +281,13 @@ Additionally, the following REST endpoints can be handled for GET requests:
|
||||
^/_matrix/client/(api/v1|r0|v3|unstable)/pushrules/
|
||||
^/_matrix/client/unstable/org.matrix.msc4140/delayed_events
|
||||
|
||||
# Account data requests
|
||||
^/_matrix/client/(r0|v3|unstable)/.*/tags
|
||||
^/_matrix/client/(r0|v3|unstable)/.*/account_data
|
||||
|
||||
# Presence requests
|
||||
^/_matrix/client/(api/v1|r0|v3|unstable)/presence/
|
||||
|
||||
Pagination requests can also be handled, but all requests for a given
|
||||
room must be routed to the same instance. Additionally, care must be taken to
|
||||
ensure that the purge history admin API is not used while pagination requests
|
||||
|
||||
466
poetry.lock
generated
466
poetry.lock
generated
@@ -1,4 +1,4 @@
|
||||
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
|
||||
# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand.
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
@@ -32,13 +32,13 @@ tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"]
|
||||
|
||||
[[package]]
|
||||
name = "authlib"
|
||||
version = "1.3.2"
|
||||
version = "1.4.0"
|
||||
description = "The ultimate Python library in building OAuth and OpenID Connect servers and clients."
|
||||
optional = true
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "Authlib-1.3.2-py2.py3-none-any.whl", hash = "sha256:ede026a95e9f5cdc2d4364a52103f5405e75aa156357e831ef2bfd0bc5094dfc"},
|
||||
{file = "authlib-1.3.2.tar.gz", hash = "sha256:4b16130117f9eb82aa6eec97f6dd4673c3f960ac0283ccdae2897ee4bc030ba2"},
|
||||
{file = "Authlib-1.4.0-py2.py3-none-any.whl", hash = "sha256:4bb20b978c8b636222b549317c1815e1fe62234fc1c5efe8855d84aebf3a74e3"},
|
||||
{file = "authlib-1.4.0.tar.gz", hash = "sha256:1c1e6608b5ed3624aeeee136ca7f8c120d6f51f731aa152b153d54741840e1f2"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1314,38 +1314,43 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "mypy"
|
||||
version = "1.11.2"
|
||||
version = "1.12.1"
|
||||
description = "Optional static typing for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "mypy-1.11.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d42a6dd818ffce7be66cce644f1dff482f1d97c53ca70908dff0b9ddc120b77a"},
|
||||
{file = "mypy-1.11.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:801780c56d1cdb896eacd5619a83e427ce436d86a3bdf9112527f24a66618fef"},
|
||||
{file = "mypy-1.11.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:41ea707d036a5307ac674ea172875f40c9d55c5394f888b168033177fce47383"},
|
||||
{file = "mypy-1.11.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6e658bd2d20565ea86da7d91331b0eed6d2eee22dc031579e6297f3e12c758c8"},
|
||||
{file = "mypy-1.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:478db5f5036817fe45adb7332d927daa62417159d49783041338921dcf646fc7"},
|
||||
{file = "mypy-1.11.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75746e06d5fa1e91bfd5432448d00d34593b52e7e91a187d981d08d1f33d4385"},
|
||||
{file = "mypy-1.11.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a976775ab2256aadc6add633d44f100a2517d2388906ec4f13231fafbb0eccca"},
|
||||
{file = "mypy-1.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd953f221ac1379050a8a646585a29574488974f79d8082cedef62744f0a0104"},
|
||||
{file = "mypy-1.11.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:57555a7715c0a34421013144a33d280e73c08df70f3a18a552938587ce9274f4"},
|
||||
{file = "mypy-1.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:36383a4fcbad95f2657642a07ba22ff797de26277158f1cc7bd234821468b1b6"},
|
||||
{file = "mypy-1.11.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e8960dbbbf36906c5c0b7f4fbf2f0c7ffb20f4898e6a879fcf56a41a08b0d318"},
|
||||
{file = "mypy-1.11.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06d26c277962f3fb50e13044674aa10553981ae514288cb7d0a738f495550b36"},
|
||||
{file = "mypy-1.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e7184632d89d677973a14d00ae4d03214c8bc301ceefcdaf5c474866814c987"},
|
||||
{file = "mypy-1.11.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3a66169b92452f72117e2da3a576087025449018afc2d8e9bfe5ffab865709ca"},
|
||||
{file = "mypy-1.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:969ea3ef09617aff826885a22ece0ddef69d95852cdad2f60c8bb06bf1f71f70"},
|
||||
{file = "mypy-1.11.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:37c7fa6121c1cdfcaac97ce3d3b5588e847aa79b580c1e922bb5d5d2902df19b"},
|
||||
{file = "mypy-1.11.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a8a53bc3ffbd161b5b2a4fff2f0f1e23a33b0168f1c0778ec70e1a3d66deb86"},
|
||||
{file = "mypy-1.11.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ff93107f01968ed834f4256bc1fc4475e2fecf6c661260066a985b52741ddce"},
|
||||
{file = "mypy-1.11.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:edb91dded4df17eae4537668b23f0ff6baf3707683734b6a818d5b9d0c0c31a1"},
|
||||
{file = "mypy-1.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:ee23de8530d99b6db0573c4ef4bd8f39a2a6f9b60655bf7a1357e585a3486f2b"},
|
||||
{file = "mypy-1.11.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:801ca29f43d5acce85f8e999b1e431fb479cb02d0e11deb7d2abb56bdaf24fd6"},
|
||||
{file = "mypy-1.11.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:af8d155170fcf87a2afb55b35dc1a0ac21df4431e7d96717621962e4b9192e70"},
|
||||
{file = "mypy-1.11.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f7821776e5c4286b6a13138cc935e2e9b6fde05e081bdebf5cdb2bb97c9df81d"},
|
||||
{file = "mypy-1.11.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:539c570477a96a4e6fb718b8d5c3e0c0eba1f485df13f86d2970c91f0673148d"},
|
||||
{file = "mypy-1.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:3f14cd3d386ac4d05c5a39a51b84387403dadbd936e17cb35882134d4f8f0d24"},
|
||||
{file = "mypy-1.11.2-py3-none-any.whl", hash = "sha256:b499bc07dbdcd3de92b0a8b29fdf592c111276f6a12fe29c30f6c417dd546d12"},
|
||||
{file = "mypy-1.11.2.tar.gz", hash = "sha256:7f9993ad3e0ffdc95c2a14b66dee63729f021968bff8ad911867579c65d13a79"},
|
||||
{file = "mypy-1.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3d7d4371829184e22fda4015278fbfdef0327a4b955a483012bd2d423a788801"},
|
||||
{file = "mypy-1.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f59f1dfbf497d473201356966e353ef09d4daec48caeacc0254db8ef633a28a5"},
|
||||
{file = "mypy-1.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b947097fae68004b8328c55161ac9db7d3566abfef72d9d41b47a021c2fba6b1"},
|
||||
{file = "mypy-1.12.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:96af62050971c5241afb4701c15189ea9507db89ad07794a4ee7b4e092dc0627"},
|
||||
{file = "mypy-1.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:d90da248f4c2dba6c44ddcfea94bb361e491962f05f41990ff24dbd09969ce20"},
|
||||
{file = "mypy-1.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1230048fec1380faf240be6385e709c8570604d2d27ec6ca7e573e3bc09c3735"},
|
||||
{file = "mypy-1.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:02dcfe270c6ea13338210908f8cadc8d31af0f04cee8ca996438fe6a97b4ec66"},
|
||||
{file = "mypy-1.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5a437c9102a6a252d9e3a63edc191a3aed5f2fcb786d614722ee3f4472e33f6"},
|
||||
{file = "mypy-1.12.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:186e0c8346efc027ee1f9acf5ca734425fc4f7dc2b60144f0fbe27cc19dc7931"},
|
||||
{file = "mypy-1.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:673ba1140a478b50e6d265c03391702fa11a5c5aff3f54d69a62a48da32cb811"},
|
||||
{file = "mypy-1.12.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9fb83a7be97c498176fb7486cafbb81decccaef1ac339d837c377b0ce3743a7f"},
|
||||
{file = "mypy-1.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:389e307e333879c571029d5b93932cf838b811d3f5395ed1ad05086b52148fb0"},
|
||||
{file = "mypy-1.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:94b2048a95a21f7a9ebc9fbd075a4fcd310410d078aa0228dbbad7f71335e042"},
|
||||
{file = "mypy-1.12.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ee5932370ccf7ebf83f79d1c157a5929d7ea36313027b0d70a488493dc1b179"},
|
||||
{file = "mypy-1.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:19bf51f87a295e7ab2894f1d8167622b063492d754e69c3c2fed6563268cb42a"},
|
||||
{file = "mypy-1.12.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d34167d43613ffb1d6c6cdc0cc043bb106cac0aa5d6a4171f77ab92a3c758bcc"},
|
||||
{file = "mypy-1.12.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:427878aa54f2e2c5d8db31fa9010c599ed9f994b3b49e64ae9cd9990c40bd635"},
|
||||
{file = "mypy-1.12.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5fcde63ea2c9f69d6be859a1e6dd35955e87fa81de95bc240143cf00de1f7f81"},
|
||||
{file = "mypy-1.12.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d54d840f6c052929f4a3d2aab2066af0f45a020b085fe0e40d4583db52aab4e4"},
|
||||
{file = "mypy-1.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:20db6eb1ca3d1de8ece00033b12f793f1ea9da767334b7e8c626a4872090cf02"},
|
||||
{file = "mypy-1.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b16fe09f9c741d85a2e3b14a5257a27a4f4886c171d562bc5a5e90d8591906b8"},
|
||||
{file = "mypy-1.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0dcc1e843d58f444fce19da4cce5bd35c282d4bde232acdeca8279523087088a"},
|
||||
{file = "mypy-1.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e10ba7de5c616e44ad21005fa13450cd0de7caaa303a626147d45307492e4f2d"},
|
||||
{file = "mypy-1.12.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0e6fe449223fa59fbee351db32283838a8fee8059e0028e9e6494a03802b4004"},
|
||||
{file = "mypy-1.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:dc6e2a2195a290a7fd5bac3e60b586d77fc88e986eba7feced8b778c373f9afe"},
|
||||
{file = "mypy-1.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:de5b2a8988b4e1269a98beaf0e7cc71b510d050dce80c343b53b4955fff45f19"},
|
||||
{file = "mypy-1.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:843826966f1d65925e8b50d2b483065c51fc16dc5d72647e0236aae51dc8d77e"},
|
||||
{file = "mypy-1.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9fe20f89da41a95e14c34b1ddb09c80262edcc295ad891f22cc4b60013e8f78d"},
|
||||
{file = "mypy-1.12.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8135ffec02121a75f75dc97c81af7c14aa4ae0dda277132cfcd6abcd21551bfd"},
|
||||
{file = "mypy-1.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:a7b76fa83260824300cc4834a3ab93180db19876bce59af921467fd03e692810"},
|
||||
{file = "mypy-1.12.1-py3-none-any.whl", hash = "sha256:ce561a09e3bb9863ab77edf29ae3a50e65685ad74bba1431278185b7e5d5486e"},
|
||||
{file = "mypy-1.12.1.tar.gz", hash = "sha256:f5b3936f7a6d0e8280c9bdef94c7ce4847f5cdfc258fbb2c29a8c1711e8bb96d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1372,17 +1377,17 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "mypy-zope"
|
||||
version = "1.0.8"
|
||||
version = "1.0.9"
|
||||
description = "Plugin for mypy to support zope interfaces"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
files = [
|
||||
{file = "mypy_zope-1.0.8-py3-none-any.whl", hash = "sha256:8794a77dae0c7e2f28b8ac48569091310b3ee45bb9d6cd4797dcb837c40f9976"},
|
||||
{file = "mypy_zope-1.0.8.tar.gz", hash = "sha256:854303a95aefc4289e8a0796808e002c2c7ecde0a10a8f7b8f48092f94ef9b9f"},
|
||||
{file = "mypy_zope-1.0.9-py3-none-any.whl", hash = "sha256:6666c1556891a3cb186137519dbd7a58cb30fb72b2504798cad47b35391921ba"},
|
||||
{file = "mypy_zope-1.0.9.tar.gz", hash = "sha256:37d6985dfb05a4c27b35cff47577fd5bad878db4893ddedf54d165f7389a1cdb"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
mypy = ">=1.0.0,<1.13.0"
|
||||
mypy = ">=1.0.0,<1.14.0"
|
||||
"zope.interface" = "*"
|
||||
"zope.schema" = "*"
|
||||
|
||||
@@ -1454,95 +1459,90 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "10.4.0"
|
||||
version = "11.0.0"
|
||||
description = "Python Imaging Library (Fork)"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
files = [
|
||||
{file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"},
|
||||
{file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"},
|
||||
{file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"},
|
||||
{file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"},
|
||||
{file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"},
|
||||
{file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"},
|
||||
{file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"},
|
||||
{file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"},
|
||||
{file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"},
|
||||
{file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"},
|
||||
{file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"},
|
||||
{file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"},
|
||||
{file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"},
|
||||
{file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"},
|
||||
{file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"},
|
||||
{file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"},
|
||||
{file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"},
|
||||
{file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"},
|
||||
{file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"},
|
||||
{file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"},
|
||||
{file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"},
|
||||
{file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"},
|
||||
{file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"},
|
||||
{file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"},
|
||||
{file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"},
|
||||
{file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"},
|
||||
{file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"},
|
||||
{file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"},
|
||||
{file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"},
|
||||
{file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"},
|
||||
{file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"},
|
||||
{file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"},
|
||||
{file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"},
|
||||
{file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"},
|
||||
{file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"},
|
||||
{file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"},
|
||||
{file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"},
|
||||
{file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"},
|
||||
{file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"},
|
||||
{file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"},
|
||||
{file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"},
|
||||
{file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"},
|
||||
{file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"},
|
||||
{file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"},
|
||||
{file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"},
|
||||
{file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"},
|
||||
{file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"},
|
||||
{file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"},
|
||||
{file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"},
|
||||
{file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"},
|
||||
{file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"},
|
||||
{file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"},
|
||||
{file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"},
|
||||
{file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"},
|
||||
{file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"},
|
||||
{file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"},
|
||||
{file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"},
|
||||
{file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"},
|
||||
{file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"},
|
||||
{file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"},
|
||||
{file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"},
|
||||
{file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"},
|
||||
{file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"},
|
||||
{file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"},
|
||||
{file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"},
|
||||
{file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"},
|
||||
{file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"},
|
||||
{file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"},
|
||||
{file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"},
|
||||
{file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"},
|
||||
{file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"},
|
||||
{file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"},
|
||||
{file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"},
|
||||
{file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"},
|
||||
{file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"},
|
||||
{file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"},
|
||||
{file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"},
|
||||
{file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"},
|
||||
{file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"},
|
||||
{file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"},
|
||||
{file = "pillow-11.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947"},
|
||||
{file = "pillow-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba"},
|
||||
{file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a65149d8ada1055029fcb665452b2814fe7d7082fcb0c5bed6db851cb69b2086"},
|
||||
{file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9"},
|
||||
{file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c26845094b1af3c91852745ae78e3ea47abf3dbcd1cf962f16b9a5fbe3ee8488"},
|
||||
{file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f"},
|
||||
{file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:674629ff60030d144b7bca2b8330225a9b11c482ed408813924619c6f302fdbb"},
|
||||
{file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:598b4e238f13276e0008299bd2482003f48158e2b11826862b1eb2ad7c768b97"},
|
||||
{file = "pillow-11.0.0-cp310-cp310-win32.whl", hash = "sha256:9a0f748eaa434a41fccf8e1ee7a3eed68af1b690e75328fd7a60af123c193b50"},
|
||||
{file = "pillow-11.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:a5629742881bcbc1f42e840af185fd4d83a5edeb96475a575f4da50d6ede337c"},
|
||||
{file = "pillow-11.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:ee217c198f2e41f184f3869f3e485557296d505b5195c513b2bfe0062dc537f1"},
|
||||
{file = "pillow-11.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc"},
|
||||
{file = "pillow-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a"},
|
||||
{file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3"},
|
||||
{file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4dba50cfa56f910241eb7f883c20f1e7b1d8f7d91c750cd0b318bad443f4d5"},
|
||||
{file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5ddbfd761ee00c12ee1be86c9c0683ecf5bb14c9772ddbd782085779a63dd55b"},
|
||||
{file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:45c566eb10b8967d71bf1ab8e4a525e5a93519e29ea071459ce517f6b903d7fa"},
|
||||
{file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b4fd7bd29610a83a8c9b564d457cf5bd92b4e11e79a4ee4716a63c959699b306"},
|
||||
{file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cb929ca942d0ec4fac404cbf520ee6cac37bf35be479b970c4ffadf2b6a1cad9"},
|
||||
{file = "pillow-11.0.0-cp311-cp311-win32.whl", hash = "sha256:006bcdd307cc47ba43e924099a038cbf9591062e6c50e570819743f5607404f5"},
|
||||
{file = "pillow-11.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:52a2d8323a465f84faaba5236567d212c3668f2ab53e1c74c15583cf507a0291"},
|
||||
{file = "pillow-11.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:16095692a253047fe3ec028e951fa4221a1f3ed3d80c397e83541a3037ff67c9"},
|
||||
{file = "pillow-11.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2c0a187a92a1cb5ef2c8ed5412dd8d4334272617f532d4ad4de31e0495bd923"},
|
||||
{file = "pillow-11.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:084a07ef0821cfe4858fe86652fffac8e187b6ae677e9906e192aafcc1b69903"},
|
||||
{file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8069c5179902dcdce0be9bfc8235347fdbac249d23bd90514b7a47a72d9fecf4"},
|
||||
{file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f02541ef64077f22bf4924f225c0fd1248c168f86e4b7abdedd87d6ebaceab0f"},
|
||||
{file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fcb4621042ac4b7865c179bb972ed0da0218a076dc1820ffc48b1d74c1e37fe9"},
|
||||
{file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:00177a63030d612148e659b55ba99527803288cea7c75fb05766ab7981a8c1b7"},
|
||||
{file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8853a3bf12afddfdf15f57c4b02d7ded92c7a75a5d7331d19f4f9572a89c17e6"},
|
||||
{file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3107c66e43bda25359d5ef446f59c497de2b5ed4c7fdba0894f8d6cf3822dafc"},
|
||||
{file = "pillow-11.0.0-cp312-cp312-win32.whl", hash = "sha256:86510e3f5eca0ab87429dd77fafc04693195eec7fd6a137c389c3eeb4cfb77c6"},
|
||||
{file = "pillow-11.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:8ec4a89295cd6cd4d1058a5e6aec6bf51e0eaaf9714774e1bfac7cfc9051db47"},
|
||||
{file = "pillow-11.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:27a7860107500d813fcd203b4ea19b04babe79448268403172782754870dac25"},
|
||||
{file = "pillow-11.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699"},
|
||||
{file = "pillow-11.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38"},
|
||||
{file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2"},
|
||||
{file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2"},
|
||||
{file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527"},
|
||||
{file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa"},
|
||||
{file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f"},
|
||||
{file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb"},
|
||||
{file = "pillow-11.0.0-cp313-cp313-win32.whl", hash = "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798"},
|
||||
{file = "pillow-11.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de"},
|
||||
{file = "pillow-11.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84"},
|
||||
{file = "pillow-11.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b"},
|
||||
{file = "pillow-11.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003"},
|
||||
{file = "pillow-11.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2"},
|
||||
{file = "pillow-11.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a"},
|
||||
{file = "pillow-11.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8"},
|
||||
{file = "pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8"},
|
||||
{file = "pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904"},
|
||||
{file = "pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3"},
|
||||
{file = "pillow-11.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2e46773dc9f35a1dd28bd6981332fd7f27bec001a918a72a79b4133cf5291dba"},
|
||||
{file = "pillow-11.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2679d2258b7f1192b378e2893a8a0a0ca472234d4c2c0e6bdd3380e8dfa21b6a"},
|
||||
{file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda2616eb2313cbb3eebbe51f19362eb434b18e3bb599466a1ffa76a033fb916"},
|
||||
{file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ec184af98a121fb2da42642dea8a29ec80fc3efbaefb86d8fdd2606619045d"},
|
||||
{file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:8594f42df584e5b4bb9281799698403f7af489fba84c34d53d1c4bfb71b7c4e7"},
|
||||
{file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:c12b5ae868897c7338519c03049a806af85b9b8c237b7d675b8c5e089e4a618e"},
|
||||
{file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:70fbbdacd1d271b77b7721fe3cdd2d537bbbd75d29e6300c672ec6bb38d9672f"},
|
||||
{file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5178952973e588b3f1360868847334e9e3bf49d19e169bbbdfaf8398002419ae"},
|
||||
{file = "pillow-11.0.0-cp39-cp39-win32.whl", hash = "sha256:8c676b587da5673d3c75bd67dd2a8cdfeb282ca38a30f37950511766b26858c4"},
|
||||
{file = "pillow-11.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:94f3e1780abb45062287b4614a5bc0874519c86a777d4a7ad34978e86428b8dd"},
|
||||
{file = "pillow-11.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:290f2cc809f9da7d6d622550bbf4c1e57518212da51b6a30fe8e0a270a5b78bd"},
|
||||
{file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1187739620f2b365de756ce086fdb3604573337cc28a0d3ac4a01ab6b2d2a6d2"},
|
||||
{file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbbcb7b57dc9c794843e3d1258c0fbf0f48656d46ffe9e09b63bbd6e8cd5d0a2"},
|
||||
{file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d203af30149ae339ad1b4f710d9844ed8796e97fda23ffbc4cc472968a47d0b"},
|
||||
{file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a0d3b115009ebb8ac3d2ebec5c2982cc693da935f4ab7bb5c8ebe2f47d36f2"},
|
||||
{file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:73853108f56df97baf2bb8b522f3578221e56f646ba345a372c78326710d3830"},
|
||||
{file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e58876c91f97b0952eb766123bfef372792ab3f4e3e1f1a2267834c2ab131734"},
|
||||
{file = "pillow-11.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:224aaa38177597bb179f3ec87eeefcce8e4f85e608025e9cfac60de237ba6316"},
|
||||
{file = "pillow-11.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5bd2d3bdb846d757055910f0a59792d33b555800813c3b39ada1829c372ccb06"},
|
||||
{file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:375b8dd15a1f5d2feafff536d47e22f69625c1aa92f12b339ec0b2ca40263273"},
|
||||
{file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:daffdf51ee5db69a82dd127eabecce20729e21f7a3680cf7cbb23f0829189790"},
|
||||
{file = "pillow-11.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7326a1787e3c7b0429659e0a944725e1b03eeaa10edd945a86dead1913383944"},
|
||||
{file = "pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"]
|
||||
docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"]
|
||||
fpx = ["olefile"]
|
||||
mic = ["olefile"]
|
||||
tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
|
||||
@@ -1660,22 +1660,19 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.9.2"
|
||||
version = "2.10.3"
|
||||
description = "Data validation using Python type hints"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"},
|
||||
{file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"},
|
||||
{file = "pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d"},
|
||||
{file = "pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
annotated-types = ">=0.6.0"
|
||||
pydantic-core = "2.23.4"
|
||||
typing-extensions = [
|
||||
{version = ">=4.12.2", markers = "python_version >= \"3.13\""},
|
||||
{version = ">=4.6.1", markers = "python_version < \"3.13\""},
|
||||
]
|
||||
pydantic-core = "2.27.1"
|
||||
typing-extensions = ">=4.12.2"
|
||||
|
||||
[package.extras]
|
||||
email = ["email-validator (>=2.0.0)"]
|
||||
@@ -1683,100 +1680,111 @@ timezone = ["tzdata"]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.23.4"
|
||||
version = "2.27.1"
|
||||
description = "Core functionality for Pydantic validation and serialization"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"},
|
||||
{file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"},
|
||||
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"},
|
||||
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"},
|
||||
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"},
|
||||
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"},
|
||||
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"},
|
||||
{file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"},
|
||||
{file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"},
|
||||
{file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"},
|
||||
{file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"},
|
||||
{file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"},
|
||||
{file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"},
|
||||
{file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"},
|
||||
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"},
|
||||
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"},
|
||||
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"},
|
||||
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"},
|
||||
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"},
|
||||
{file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"},
|
||||
{file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"},
|
||||
{file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"},
|
||||
{file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"},
|
||||
{file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"},
|
||||
{file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"},
|
||||
{file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"},
|
||||
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"},
|
||||
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"},
|
||||
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"},
|
||||
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"},
|
||||
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"},
|
||||
{file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"},
|
||||
{file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"},
|
||||
{file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"},
|
||||
{file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"},
|
||||
{file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"},
|
||||
{file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"},
|
||||
{file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"},
|
||||
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"},
|
||||
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"},
|
||||
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"},
|
||||
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"},
|
||||
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"},
|
||||
{file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"},
|
||||
{file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"},
|
||||
{file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"},
|
||||
{file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"},
|
||||
{file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"},
|
||||
{file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"},
|
||||
{file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"},
|
||||
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"},
|
||||
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"},
|
||||
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"},
|
||||
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"},
|
||||
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"},
|
||||
{file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"},
|
||||
{file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"},
|
||||
{file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"},
|
||||
{file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"},
|
||||
{file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"},
|
||||
{file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"},
|
||||
{file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"},
|
||||
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"},
|
||||
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"},
|
||||
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"},
|
||||
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"},
|
||||
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"},
|
||||
{file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"},
|
||||
{file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"},
|
||||
{file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"},
|
||||
{file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"},
|
||||
{file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"},
|
||||
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"},
|
||||
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"},
|
||||
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"},
|
||||
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"},
|
||||
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"},
|
||||
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"},
|
||||
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"},
|
||||
{file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"},
|
||||
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"},
|
||||
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"},
|
||||
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"},
|
||||
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"},
|
||||
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"},
|
||||
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"},
|
||||
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"},
|
||||
{file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"},
|
||||
{file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c"},
|
||||
{file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206"},
|
||||
{file = "pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c"},
|
||||
{file = "pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c"},
|
||||
{file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc"},
|
||||
{file = "pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9"},
|
||||
{file = "pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5"},
|
||||
{file = "pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb"},
|
||||
{file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae"},
|
||||
{file = "pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c"},
|
||||
{file = "pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16"},
|
||||
{file = "pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960"},
|
||||
{file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23"},
|
||||
{file = "pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05"},
|
||||
{file = "pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337"},
|
||||
{file = "pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:5897bec80a09b4084aee23f9b73a9477a46c3304ad1d2d07acca19723fb1de62"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d0165ab2914379bd56908c02294ed8405c252250668ebcb438a55494c69f44ab"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b9af86e1d8e4cfc82c2022bfaa6f459381a50b94a29e95dcdda8442d6d83864"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f6c8a66741c5f5447e047ab0ba7a1c61d1e95580d64bce852e3df1f895c4067"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a42d6a8156ff78981f8aa56eb6394114e0dedb217cf8b729f438f643608cbcd"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64c65f40b4cd8b0e049a8edde07e38b476da7e3aaebe63287c899d2cff253fa5"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdcf339322a3fae5cbd504edcefddd5a50d9ee00d968696846f089b4432cf78"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf99c8404f008750c846cb4ac4667b798a9f7de673ff719d705d9b2d6de49c5f"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f1edcea27918d748c7e5e4d917297b2a0ab80cad10f86631e488b7cddf76a36"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:159cac0a3d096f79ab6a44d77a961917219707e2a130739c64d4dd46281f5c2a"},
|
||||
{file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:029d9757eb621cc6e1848fa0b0310310de7301057f623985698ed7ebb014391b"},
|
||||
{file = "pydantic_core-2.27.1-cp38-none-win32.whl", hash = "sha256:a28af0695a45f7060e6f9b7092558a928a28553366519f64083c63a44f70e618"},
|
||||
{file = "pydantic_core-2.27.1-cp38-none-win_amd64.whl", hash = "sha256:2d4567c850905d5eaaed2f7a404e61012a51caf288292e016360aa2b96ff38d4"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e9386266798d64eeb19dd3677051f5705bf873e98e15897ddb7d76f477131967"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4228b5b646caa73f119b1ae756216b59cc6e2267201c27d3912b592c5e323b60"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3dfe500de26c52abe0477dde16192ac39c98f05bf2d80e76102d394bd13854"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aee66be87825cdf72ac64cb03ad4c15ffef4143dbf5c113f64a5ff4f81477bf9"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b748c44bb9f53031c8cbc99a8a061bc181c1000c60a30f55393b6e9c45cc5bd"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ca038c7f6a0afd0b2448941b6ef9d5e1949e999f9e5517692eb6da58e9d44be"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bd57539da59a3e4671b90a502da9a28c72322a4f17866ba3ac63a82c4498e"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac6c2c45c847bbf8f91930d88716a0fb924b51e0c6dad329b793d670ec5db792"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b94d4ba43739bbe8b0ce4262bcc3b7b9f31459ad120fb595627eaeb7f9b9ca01"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:00e6424f4b26fe82d44577b4c842d7df97c20be6439e8e685d0d715feceb9fb9"},
|
||||
{file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38de0a70160dd97540335b7ad3a74571b24f1dc3ed33f815f0880682e6880131"},
|
||||
{file = "pydantic_core-2.27.1-cp39-none-win32.whl", hash = "sha256:7ccebf51efc61634f6c2344da73e366c75e735960b5654b63d7e6f69a5885fa3"},
|
||||
{file = "pydantic_core-2.27.1-cp39-none-win_amd64.whl", hash = "sha256:a57847b090d7892f123726202b7daa20df6694cbd583b67a592e856bff603d6c"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f"},
|
||||
{file = "pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5fde892e6c697ce3e30c61b239330fc5d569a71fefd4eb6512fc6caec9dd9e2f"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:816f5aa087094099fff7edabb5e01cc370eb21aa1a1d44fe2d2aefdfb5599b31"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c10c309e18e443ddb108f0ef64e8729363adbfd92d6d57beec680f6261556f3"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98476c98b02c8e9b2eec76ac4156fd006628b1b2d0ef27e548ffa978393fd154"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3027001c28434e7ca5a6e1e527487051136aa81803ac812be51802150d880dd"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7699b1df36a48169cdebda7ab5a2bac265204003f153b4bd17276153d997670a"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1c39b07d90be6b48968ddc8c19e7585052088fd7ec8d568bb31ff64c70ae3c97"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:46ccfe3032b3915586e469d4972973f893c0a2bb65669194a5bdea9bacc088c2"},
|
||||
{file = "pydantic_core-2.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:62ba45e21cf6571d7f716d903b5b7b6d2617e2d5d67c0923dc47b9d41369f840"},
|
||||
{file = "pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1954,13 +1962,13 @@ six = ">=1.5"
|
||||
|
||||
[[package]]
|
||||
name = "python-multipart"
|
||||
version = "0.0.16"
|
||||
version = "0.0.18"
|
||||
description = "A streaming multipart parser for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "python_multipart-0.0.16-py3-none-any.whl", hash = "sha256:c2759b7b976ef3937214dfb592446b59dfaa5f04682a076f78b117c94776d87a"},
|
||||
{file = "python_multipart-0.0.16.tar.gz", hash = "sha256:8dee37b88dab9b59922ca173c35acb627cc12ec74019f5cd4578369c6df36554"},
|
||||
{file = "python_multipart-0.0.18-py3-none-any.whl", hash = "sha256:efe91480f485f6a361427a541db4796f9e1591afc0fb8e7a4ba06bfbc6708996"},
|
||||
{file = "python_multipart-0.0.18.tar.gz", hash = "sha256:7a68db60c8bfb82e460637fa4750727b45af1d5e2ed215593f917f64694d34fe"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2619,19 +2627,20 @@ docs = ["sphinx (<7.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "twine"
|
||||
version = "5.1.1"
|
||||
version = "6.0.1"
|
||||
description = "Collection of utilities for publishing packages on PyPI"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
files = [
|
||||
{file = "twine-5.1.1-py3-none-any.whl", hash = "sha256:215dbe7b4b94c2c50a7315c0275d2258399280fbb7d04182c7e55e24b5f93997"},
|
||||
{file = "twine-5.1.1.tar.gz", hash = "sha256:9aa0825139c02b3434d913545c7b847a21c835e11597f5255842d457da2322db"},
|
||||
{file = "twine-6.0.1-py3-none-any.whl", hash = "sha256:9c6025b203b51521d53e200f4a08b116dee7500a38591668c6a6033117bdc218"},
|
||||
{file = "twine-6.0.1.tar.gz", hash = "sha256:36158b09df5406e1c9c1fb8edb24fc2be387709443e7376689b938531582ee27"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
importlib-metadata = ">=3.6"
|
||||
keyring = ">=15.1"
|
||||
pkginfo = ">=1.8.1,<1.11"
|
||||
importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""}
|
||||
keyring = {version = ">=15.1", markers = "platform_machine != \"ppc64le\" and platform_machine != \"s390x\""}
|
||||
packaging = "*"
|
||||
pkginfo = ">=1.8.1"
|
||||
readme-renderer = ">=35.0"
|
||||
requests = ">=2.20"
|
||||
requests-toolbelt = ">=0.8.0,<0.9.0 || >0.9.0"
|
||||
@@ -2639,6 +2648,9 @@ rfc3986 = ">=1.4.0"
|
||||
rich = ">=12.0.0"
|
||||
urllib3 = ">=1.26.0"
|
||||
|
||||
[package.extras]
|
||||
keyring = ["keyring (>=15.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "twisted"
|
||||
version = "24.7.0"
|
||||
|
||||
@@ -71,6 +71,34 @@ impl TryFrom<&str> for UserID {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for UserID {
|
||||
type Error = IdentifierError;
|
||||
|
||||
/// Will try creating a `UserID` from the provided `&str`.
|
||||
/// Can fail if the user_id is incorrectly formatted.
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||
if !s.starts_with('@') {
|
||||
return Err(IdentifierError::IncorrectSigil);
|
||||
}
|
||||
|
||||
if s.find(':').is_none() {
|
||||
return Err(IdentifierError::MissingColon);
|
||||
}
|
||||
|
||||
Ok(UserID(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for UserID {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let s: String = serde::Deserialize::deserialize(deserializer)?;
|
||||
UserID::try_from(s).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for UserID {
|
||||
type Target = str;
|
||||
|
||||
@@ -84,3 +112,141 @@ impl fmt::Display for UserID {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// A Matrix room_id.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct RoomID(String);
|
||||
|
||||
impl RoomID {
|
||||
/// Returns the `localpart` of the room_id.
|
||||
pub fn localpart(&self) -> &str {
|
||||
&self[1..self.colon_pos()]
|
||||
}
|
||||
|
||||
/// Returns the `server_name` / `domain` of the room_id.
|
||||
pub fn server_name(&self) -> &str {
|
||||
&self[self.colon_pos() + 1..]
|
||||
}
|
||||
|
||||
/// Returns the position of the ':' inside of the room_id.
|
||||
/// Used when splitting the room_id into it's respective parts.
|
||||
fn colon_pos(&self) -> usize {
|
||||
self.find(':').unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for RoomID {
|
||||
type Error = IdentifierError;
|
||||
|
||||
/// Will try creating a `RoomID` from the provided `&str`.
|
||||
/// Can fail if the room_id is incorrectly formatted.
|
||||
fn try_from(s: &str) -> Result<Self, Self::Error> {
|
||||
if !s.starts_with('!') {
|
||||
return Err(IdentifierError::IncorrectSigil);
|
||||
}
|
||||
|
||||
if s.find(':').is_none() {
|
||||
return Err(IdentifierError::MissingColon);
|
||||
}
|
||||
|
||||
Ok(RoomID(s.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for RoomID {
|
||||
type Error = IdentifierError;
|
||||
|
||||
/// Will try creating a `RoomID` from the provided `String`.
|
||||
/// Can fail if the room_id is incorrectly formatted.
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||
if !s.starts_with('!') {
|
||||
return Err(IdentifierError::IncorrectSigil);
|
||||
}
|
||||
|
||||
if s.find(':').is_none() {
|
||||
return Err(IdentifierError::MissingColon);
|
||||
}
|
||||
|
||||
Ok(RoomID(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for RoomID {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let s: String = serde::Deserialize::deserialize(deserializer)?;
|
||||
RoomID::try_from(s).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for RoomID {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for RoomID {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// A Matrix event_id.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct EventID(String);
|
||||
|
||||
impl TryFrom<&str> for EventID {
|
||||
type Error = IdentifierError;
|
||||
|
||||
/// Will try creating a `EventID` from the provided `&str`.
|
||||
/// Can fail if the event_id is incorrectly formatted.
|
||||
fn try_from(s: &str) -> Result<Self, Self::Error> {
|
||||
if !s.starts_with('$') {
|
||||
return Err(IdentifierError::IncorrectSigil);
|
||||
}
|
||||
|
||||
Ok(EventID(s.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for EventID {
|
||||
type Error = IdentifierError;
|
||||
|
||||
/// Will try creating a `EventID` from the provided `String`.
|
||||
/// Can fail if the event_id is incorrectly formatted.
|
||||
fn try_from(s: String) -> Result<Self, Self::Error> {
|
||||
if !s.starts_with('$') {
|
||||
return Err(IdentifierError::IncorrectSigil);
|
||||
}
|
||||
|
||||
Ok(EventID(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> serde::Deserialize<'de> for EventID {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let s: String = serde::Deserialize::deserialize(deserializer)?;
|
||||
EventID::try_from(s).map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for EventID {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for EventID {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,8 +100,9 @@ class Codes(str, Enum):
|
||||
# The account has been suspended on the server.
|
||||
# By opposition to `USER_DEACTIVATED`, this is a reversible measure
|
||||
# that can possibly be appealed and reverted.
|
||||
# Part of MSC3823.
|
||||
USER_ACCOUNT_SUSPENDED = "ORG.MATRIX.MSC3823.USER_ACCOUNT_SUSPENDED"
|
||||
# Introduced by MSC3823
|
||||
# https://github.com/matrix-org/matrix-spec-proposals/pull/3823
|
||||
USER_ACCOUNT_SUSPENDED = "M_USER_SUSPENDED"
|
||||
|
||||
BAD_ALIAS = "M_BAD_ALIAS"
|
||||
# For restricted join rules.
|
||||
|
||||
@@ -110,6 +110,7 @@ class EmailConfig(Config):
|
||||
raise ConfigError(
|
||||
"email.require_transport_security requires email.enable_tls to be true"
|
||||
)
|
||||
self.email_tlsname = email_config.get("tlsname", None)
|
||||
|
||||
if "app_name" in email_config:
|
||||
self.email_app_name = email_config["app_name"]
|
||||
|
||||
@@ -436,10 +436,6 @@ class ExperimentalConfig(Config):
|
||||
("experimental", "msc4108_delegation_endpoint"),
|
||||
)
|
||||
|
||||
self.msc3823_account_suspension = experimental.get(
|
||||
"msc3823_account_suspension", False
|
||||
)
|
||||
|
||||
# MSC4151: Report room API (Client-Server API)
|
||||
self.msc4151_enabled: bool = experimental.get("msc4151_enabled", False)
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ from unpaddedbase64 import decode_base64
|
||||
from synapse.types import JsonDict
|
||||
from synapse.util.stringutils import random_string, random_string_with_symbols
|
||||
|
||||
from ._base import Config, ConfigError
|
||||
from ._base import Config, ConfigError, read_file
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from signedjson.key import VerifyKeyWithExpiry
|
||||
@@ -91,6 +91,11 @@ To suppress this warning and continue using 'matrix.org', admins should set
|
||||
'suppress_key_server_warning' to 'true' in homeserver.yaml.
|
||||
--------------------------------------------------------------------------------"""
|
||||
|
||||
CONFLICTING_MACAROON_SECRET_KEY_OPTS_ERROR = """\
|
||||
Conflicting options 'macaroon_secret_key' and 'macaroon_secret_key_path' are
|
||||
both defined in config file.
|
||||
"""
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -166,10 +171,16 @@ class KeyConfig(Config):
|
||||
)
|
||||
)
|
||||
|
||||
macaroon_secret_key: Optional[str] = config.get(
|
||||
"macaroon_secret_key", self.root.registration.registration_shared_secret
|
||||
)
|
||||
|
||||
macaroon_secret_key = config.get("macaroon_secret_key")
|
||||
macaroon_secret_key_path = config.get("macaroon_secret_key_path")
|
||||
if macaroon_secret_key_path:
|
||||
if macaroon_secret_key:
|
||||
raise ConfigError(CONFLICTING_MACAROON_SECRET_KEY_OPTS_ERROR)
|
||||
macaroon_secret_key = read_file(
|
||||
macaroon_secret_key_path, "macaroon_secret_key_path"
|
||||
).strip()
|
||||
if not macaroon_secret_key:
|
||||
macaroon_secret_key = self.root.registration.registration_shared_secret
|
||||
if not macaroon_secret_key:
|
||||
# Unfortunately, there are people out there that don't have this
|
||||
# set. Lets just be "nice" and derive one from their secret key.
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
import logging
|
||||
import os
|
||||
from typing import Any, Dict, List, Tuple
|
||||
from urllib.request import getproxies_environment # type: ignore
|
||||
from urllib.request import getproxies_environment
|
||||
|
||||
import attr
|
||||
|
||||
|
||||
@@ -248,7 +248,7 @@ class EventContext(UnpersistedEventContextBase):
|
||||
@tag_args
|
||||
async def get_current_state_ids(
|
||||
self, state_filter: Optional["StateFilter"] = None
|
||||
) -> Optional[StateMap[str]]:
|
||||
) -> StateMap[str]:
|
||||
"""
|
||||
Gets the room state map, including this event - ie, the state in ``state_group``
|
||||
|
||||
@@ -256,13 +256,12 @@ class EventContext(UnpersistedEventContextBase):
|
||||
not make it into the room state. This method will raise an exception if
|
||||
``rejected`` is set.
|
||||
|
||||
It is also an error to access this for an outlier event.
|
||||
|
||||
Arg:
|
||||
state_filter: specifies the type of state event to fetch from DB, example: EventTypes.JoinRules
|
||||
|
||||
Returns:
|
||||
Returns None if state_group is None, which happens when the associated
|
||||
event is an outlier.
|
||||
|
||||
Maps a (type, state_key) to the event ID of the state event matching
|
||||
this tuple.
|
||||
"""
|
||||
@@ -300,7 +299,8 @@ class EventContext(UnpersistedEventContextBase):
|
||||
this tuple.
|
||||
"""
|
||||
|
||||
assert self.state_group_before_event is not None
|
||||
if self.state_group_before_event is None:
|
||||
return {}
|
||||
return await self._storage.state.get_state_ids_for_group(
|
||||
self.state_group_before_event, state_filter
|
||||
)
|
||||
|
||||
@@ -473,7 +473,7 @@ class AdminHandler:
|
||||
"type": EventTypes.Redaction,
|
||||
"content": {"reason": reason} if reason else {},
|
||||
"room_id": room,
|
||||
"sender": user_id,
|
||||
"sender": requester.user.to_string(),
|
||||
}
|
||||
if room_version.updated_redaction_rules:
|
||||
event_dict["content"]["redacts"] = event.event_id
|
||||
|
||||
@@ -896,10 +896,10 @@ class ApplicationServicesHandler:
|
||||
results = await make_deferred_yieldable(
|
||||
defer.DeferredList(
|
||||
[
|
||||
run_in_background(
|
||||
run_in_background( # type: ignore[call-overload]
|
||||
self.appservice_api.claim_client_keys,
|
||||
# We know this must be an app service.
|
||||
self.store.get_app_service_by_id(service_id), # type: ignore[arg-type]
|
||||
self.store.get_app_service_by_id(service_id),
|
||||
service_query,
|
||||
)
|
||||
for service_id, service_query in query_by_appservice.items()
|
||||
@@ -952,10 +952,10 @@ class ApplicationServicesHandler:
|
||||
results = await make_deferred_yieldable(
|
||||
defer.DeferredList(
|
||||
[
|
||||
run_in_background(
|
||||
run_in_background( # type: ignore[call-overload]
|
||||
self.appservice_api.query_keys,
|
||||
# We know this must be an app service.
|
||||
self.store.get_app_service_by_id(service_id), # type: ignore[arg-type]
|
||||
self.store.get_app_service_by_id(service_id),
|
||||
service_query,
|
||||
)
|
||||
for service_id, service_query in query_by_appservice.items()
|
||||
|
||||
@@ -47,15 +47,45 @@ logger = logging.getLogger(__name__)
|
||||
_is_old_twisted = parse_version(twisted.__version__) < parse_version("21")
|
||||
|
||||
|
||||
class _NoTLSESMTPSender(ESMTPSender):
|
||||
"""Extend ESMTPSender to disable TLS
|
||||
class _BackportESMTPSender(ESMTPSender):
|
||||
"""Extend old versions of ESMTPSender to configure TLS.
|
||||
|
||||
Unfortunately, before Twisted 21.2, ESMTPSender doesn't give an easy way to disable
|
||||
TLS, so we override its internal method which it uses to generate a context factory.
|
||||
Unfortunately, before Twisted 21.2, ESMTPSender doesn't give an easy way to
|
||||
disable TLS, or to configure the hostname used for TLS certificate validation.
|
||||
This backports the `hostname` parameter for that functionality.
|
||||
"""
|
||||
|
||||
__hostname: Optional[str]
|
||||
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
""""""
|
||||
self.__hostname = kwargs.pop("hostname", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def _getContextFactory(self) -> Optional[IOpenSSLContextFactory]:
|
||||
return None
|
||||
if self.context is not None:
|
||||
return self.context
|
||||
elif self.__hostname is None:
|
||||
return None # disable TLS if hostname is None
|
||||
return optionsForClientTLS(self.__hostname)
|
||||
|
||||
|
||||
class _BackportESMTPSenderFactory(ESMTPSenderFactory):
|
||||
"""An ESMTPSenderFactory for _BackportESMTPSender.
|
||||
|
||||
This backports the `hostname` parameter, to disable or configure TLS.
|
||||
"""
|
||||
|
||||
__hostname: Optional[str]
|
||||
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
self.__hostname = kwargs.pop("hostname", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
def protocol(self, *args: Any, **kwargs: Any) -> ESMTPSender: # type: ignore
|
||||
# this overrides ESMTPSenderFactory's `protocol` attribute, with a Callable
|
||||
# instantiating our _BackportESMTPSender, providing the hostname parameter
|
||||
return _BackportESMTPSender(*args, **kwargs, hostname=self.__hostname)
|
||||
|
||||
|
||||
async def _sendmail(
|
||||
@@ -71,6 +101,7 @@ async def _sendmail(
|
||||
require_tls: bool = False,
|
||||
enable_tls: bool = True,
|
||||
force_tls: bool = False,
|
||||
tlsname: Optional[str] = None,
|
||||
) -> None:
|
||||
"""A simple wrapper around ESMTPSenderFactory, to allow substitution in tests
|
||||
|
||||
@@ -88,39 +119,33 @@ async def _sendmail(
|
||||
enable_tls: True to enable STARTTLS. If this is False and require_tls is True,
|
||||
the request will fail.
|
||||
force_tls: True to enable Implicit TLS.
|
||||
tlsname: the domain name expected as the TLS certificate's commonname,
|
||||
defaults to smtphost.
|
||||
"""
|
||||
msg = BytesIO(msg_bytes)
|
||||
d: "Deferred[object]" = Deferred()
|
||||
if not enable_tls:
|
||||
tlsname = None
|
||||
elif tlsname is None:
|
||||
tlsname = smtphost
|
||||
|
||||
def build_sender_factory(**kwargs: Any) -> ESMTPSenderFactory:
|
||||
return ESMTPSenderFactory(
|
||||
username,
|
||||
password,
|
||||
from_addr,
|
||||
to_addr,
|
||||
msg,
|
||||
d,
|
||||
heloFallback=True,
|
||||
requireAuthentication=require_auth,
|
||||
requireTransportSecurity=require_tls,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
factory: IProtocolFactory
|
||||
if _is_old_twisted:
|
||||
# before twisted 21.2, we have to override the ESMTPSender protocol to disable
|
||||
# TLS
|
||||
factory = build_sender_factory()
|
||||
|
||||
if not enable_tls:
|
||||
factory.protocol = _NoTLSESMTPSender
|
||||
else:
|
||||
# for twisted 21.2 and later, there is a 'hostname' parameter which we should
|
||||
# set to enable TLS.
|
||||
factory = build_sender_factory(hostname=smtphost if enable_tls else None)
|
||||
factory: IProtocolFactory = (
|
||||
_BackportESMTPSenderFactory if _is_old_twisted else ESMTPSenderFactory
|
||||
)(
|
||||
username,
|
||||
password,
|
||||
from_addr,
|
||||
to_addr,
|
||||
msg,
|
||||
d,
|
||||
heloFallback=True,
|
||||
requireAuthentication=require_auth,
|
||||
requireTransportSecurity=require_tls,
|
||||
hostname=tlsname,
|
||||
)
|
||||
|
||||
if force_tls:
|
||||
factory = TLSMemoryBIOFactory(optionsForClientTLS(smtphost), True, factory)
|
||||
factory = TLSMemoryBIOFactory(optionsForClientTLS(tlsname), True, factory)
|
||||
|
||||
endpoint = HostnameEndpoint(
|
||||
reactor, smtphost, smtpport, timeout=30, bindAddress=None
|
||||
@@ -148,6 +173,7 @@ class SendEmailHandler:
|
||||
self._require_transport_security = hs.config.email.require_transport_security
|
||||
self._enable_tls = hs.config.email.enable_smtp_tls
|
||||
self._force_tls = hs.config.email.force_tls
|
||||
self._tlsname = hs.config.email.email_tlsname
|
||||
|
||||
self._sendmail = _sendmail
|
||||
|
||||
@@ -227,4 +253,5 @@ class SendEmailHandler:
|
||||
require_tls=self._require_transport_security,
|
||||
enable_tls=self._enable_tls,
|
||||
force_tls=self._force_tls,
|
||||
tlsname=self._tlsname,
|
||||
)
|
||||
|
||||
@@ -161,7 +161,7 @@ class UserDirectoryHandler(StateDeltasHandler):
|
||||
non_spammy_users = []
|
||||
for user in results["results"]:
|
||||
if not await self._spam_checker_module_callbacks.check_username_for_spam(
|
||||
user
|
||||
user, user_id
|
||||
):
|
||||
non_spammy_users.append(user)
|
||||
results["results"] = non_spammy_users
|
||||
|
||||
@@ -41,7 +41,7 @@ from canonicaljson import encode_canonical_json
|
||||
from netaddr import AddrFormatError, IPAddress, IPSet
|
||||
from prometheus_client import Counter
|
||||
from typing_extensions import Protocol
|
||||
from zope.interface import implementer, provider
|
||||
from zope.interface import implementer
|
||||
|
||||
from OpenSSL import SSL
|
||||
from OpenSSL.SSL import VERIFY_NONE
|
||||
@@ -225,7 +225,7 @@ class _IPBlockingResolver:
|
||||
recv.addressResolved(address)
|
||||
recv.resolutionComplete()
|
||||
|
||||
@provider(IResolutionReceiver)
|
||||
@implementer(IResolutionReceiver)
|
||||
class EndpointReceiver:
|
||||
@staticmethod
|
||||
def resolutionBegan(resolutionInProgress: IHostResolution) -> None:
|
||||
@@ -239,8 +239,9 @@ class _IPBlockingResolver:
|
||||
def resolutionComplete() -> None:
|
||||
_callback()
|
||||
|
||||
endpoint_receiver_wrapper = EndpointReceiver()
|
||||
self._reactor.nameResolver.resolveHostName(
|
||||
EndpointReceiver, hostname, portNumber=portNumber
|
||||
endpoint_receiver_wrapper, hostname, portNumber=portNumber
|
||||
)
|
||||
|
||||
return recv
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
import logging
|
||||
import random
|
||||
import re
|
||||
from typing import Any, Collection, Dict, List, Optional, Sequence, Tuple
|
||||
from typing import Any, Collection, Dict, List, Optional, Sequence, Tuple, Union
|
||||
from urllib.parse import urlparse
|
||||
from urllib.request import ( # type: ignore[attr-defined]
|
||||
getproxies_environment,
|
||||
@@ -351,7 +351,9 @@ def http_proxy_endpoint(
|
||||
proxy: Optional[bytes],
|
||||
reactor: IReactorCore,
|
||||
tls_options_factory: Optional[IPolicyForHTTPS],
|
||||
**kwargs: object,
|
||||
timeout: float = 30,
|
||||
bindAddress: Optional[Union[bytes, str, tuple[Union[bytes, str], int]]] = None,
|
||||
attemptDelay: Optional[float] = None,
|
||||
) -> Tuple[Optional[IStreamClientEndpoint], Optional[ProxyCredentials]]:
|
||||
"""Parses an http proxy setting and returns an endpoint for the proxy
|
||||
|
||||
@@ -382,12 +384,15 @@ def http_proxy_endpoint(
|
||||
# 3.9+) on scheme-less proxies, e.g. host:port.
|
||||
scheme, host, port, credentials = parse_proxy(proxy)
|
||||
|
||||
proxy_endpoint = HostnameEndpoint(reactor, host, port, **kwargs)
|
||||
proxy_endpoint = HostnameEndpoint(
|
||||
reactor, host, port, timeout, bindAddress, attemptDelay
|
||||
)
|
||||
|
||||
if scheme == b"https":
|
||||
if tls_options_factory:
|
||||
tls_options = tls_options_factory.creatorForNetloc(host, port)
|
||||
proxy_endpoint = wrapClientTLS(tls_options, proxy_endpoint)
|
||||
wrapped_proxy_endpoint = wrapClientTLS(tls_options, proxy_endpoint)
|
||||
return wrapped_proxy_endpoint, credentials
|
||||
else:
|
||||
raise RuntimeError(
|
||||
f"No TLS options for a https connection via proxy {proxy!s}"
|
||||
|
||||
@@ -89,7 +89,7 @@ class ReplicationEndpointFactory:
|
||||
location_config.port,
|
||||
)
|
||||
if scheme == "https":
|
||||
endpoint = wrapClientTLS(
|
||||
wrapped_endpoint = wrapClientTLS(
|
||||
# The 'port' argument below isn't actually used by the function
|
||||
self.context_factory.creatorForNetloc(
|
||||
location_config.host.encode("utf-8"),
|
||||
@@ -97,6 +97,8 @@ class ReplicationEndpointFactory:
|
||||
),
|
||||
endpoint,
|
||||
)
|
||||
return wrapped_endpoint
|
||||
|
||||
return endpoint
|
||||
elif isinstance(location_config, InstanceUnixLocationConfig):
|
||||
return UNIXClientEndpoint(self.reactor, location_config.path)
|
||||
|
||||
@@ -20,13 +20,10 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
from types import TracebackType
|
||||
from typing import Optional, Type
|
||||
from typing import Optional
|
||||
|
||||
from opentracing import Scope, ScopeManager, Span
|
||||
|
||||
import twisted
|
||||
|
||||
from synapse.logging.context import (
|
||||
LoggingContext,
|
||||
current_context,
|
||||
@@ -112,9 +109,6 @@ class _LogContextScope(Scope):
|
||||
"""
|
||||
A custom opentracing scope, associated with a LogContext
|
||||
|
||||
* filters out _DefGen_Return exceptions which arise from calling
|
||||
`defer.returnValue` in Twisted code
|
||||
|
||||
* When the scope is closed, the logcontext's active scope is reset to None.
|
||||
and - if enter_logcontext was set - the logcontext is finished too.
|
||||
"""
|
||||
@@ -146,17 +140,6 @@ class _LogContextScope(Scope):
|
||||
self._finish_on_close = finish_on_close
|
||||
self._enter_logcontext = enter_logcontext
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: Optional[Type[BaseException]],
|
||||
value: Optional[BaseException],
|
||||
traceback: Optional[TracebackType],
|
||||
) -> None:
|
||||
if exc_type == twisted.internet.defer._DefGen_Return:
|
||||
# filter out defer.returnValue() calls
|
||||
exc_type = value = traceback = None
|
||||
super().__exit__(exc_type, value, traceback)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"Scope<{self.span}>"
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ from typing import (
|
||||
Optional,
|
||||
Tuple,
|
||||
Union,
|
||||
cast,
|
||||
)
|
||||
|
||||
# `Literal` appears with Python 3.8.
|
||||
@@ -168,7 +169,10 @@ USER_MAY_PUBLISH_ROOM_CALLBACK = Callable[
|
||||
]
|
||||
],
|
||||
]
|
||||
CHECK_USERNAME_FOR_SPAM_CALLBACK = Callable[[UserProfile], Awaitable[bool]]
|
||||
CHECK_USERNAME_FOR_SPAM_CALLBACK = Union[
|
||||
Callable[[UserProfile], Awaitable[bool]],
|
||||
Callable[[UserProfile, str], Awaitable[bool]],
|
||||
]
|
||||
LEGACY_CHECK_REGISTRATION_FOR_SPAM_CALLBACK = Callable[
|
||||
[
|
||||
Optional[dict],
|
||||
@@ -716,7 +720,9 @@ class SpamCheckerModuleApiCallbacks:
|
||||
|
||||
return self.NOT_SPAM
|
||||
|
||||
async def check_username_for_spam(self, user_profile: UserProfile) -> bool:
|
||||
async def check_username_for_spam(
|
||||
self, user_profile: UserProfile, requester_id: str
|
||||
) -> bool:
|
||||
"""Checks if a user ID or display name are considered "spammy" by this server.
|
||||
|
||||
If the server considers a username spammy, then it will not be included in
|
||||
@@ -727,15 +733,33 @@ class SpamCheckerModuleApiCallbacks:
|
||||
* user_id
|
||||
* display_name
|
||||
* avatar_url
|
||||
requester_id: The user ID of the user making the user directory search request.
|
||||
|
||||
Returns:
|
||||
True if the user is spammy.
|
||||
"""
|
||||
for callback in self._check_username_for_spam_callbacks:
|
||||
with Measure(self.clock, f"{callback.__module__}.{callback.__qualname__}"):
|
||||
checker_args = inspect.signature(callback)
|
||||
# Make a copy of the user profile object to ensure the spam checker cannot
|
||||
# modify it.
|
||||
res = await delay_cancellation(callback(user_profile.copy()))
|
||||
# Also ensure backwards compatibility with spam checker callbacks
|
||||
# that don't expect the requester_id argument.
|
||||
if len(checker_args.parameters) == 2:
|
||||
callback_with_requester_id = cast(
|
||||
Callable[[UserProfile, str], Awaitable[bool]], callback
|
||||
)
|
||||
res = await delay_cancellation(
|
||||
callback_with_requester_id(user_profile.copy(), requester_id)
|
||||
)
|
||||
else:
|
||||
callback_without_requester_id = cast(
|
||||
Callable[[UserProfile], Awaitable[bool]], callback
|
||||
)
|
||||
res = await delay_cancellation(
|
||||
callback_without_requester_id(user_profile.copy())
|
||||
)
|
||||
|
||||
if res:
|
||||
return True
|
||||
|
||||
|
||||
@@ -371,7 +371,7 @@ class BulkPushRuleEvaluator:
|
||||
"Deferred[Tuple[int, Tuple[dict, Optional[int]], Dict[str, Dict[str, JsonValue]], Mapping[str, ProfileInfo]]]",
|
||||
gather_results(
|
||||
(
|
||||
run_in_background( # type: ignore[call-arg]
|
||||
run_in_background( # type: ignore[call-overload]
|
||||
self.store.get_number_joined_users_in_room,
|
||||
event.room_id, # type: ignore[arg-type]
|
||||
),
|
||||
@@ -382,10 +382,10 @@ class BulkPushRuleEvaluator:
|
||||
event_id_to_event,
|
||||
),
|
||||
run_in_background(self._related_events, event),
|
||||
run_in_background( # type: ignore[call-arg]
|
||||
run_in_background( # type: ignore[call-overload]
|
||||
self.store.get_subset_users_in_room_with_profiles,
|
||||
event.room_id, # type: ignore[arg-type]
|
||||
rules_by_user.keys(), # type: ignore[arg-type]
|
||||
event.room_id,
|
||||
rules_by_user.keys(),
|
||||
),
|
||||
),
|
||||
consumeErrors=True,
|
||||
|
||||
@@ -107,6 +107,8 @@ from synapse.rest.admin.users import (
|
||||
UserAdminServlet,
|
||||
UserByExternalId,
|
||||
UserByThreePid,
|
||||
UserInvitesCount,
|
||||
UserJoinedRoomCount,
|
||||
UserMembershipRestServlet,
|
||||
UserRegisterServlet,
|
||||
UserReplaceMasterCrossSigningKeyRestServlet,
|
||||
@@ -323,6 +325,8 @@ def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
|
||||
UserByThreePid(hs).register(http_server)
|
||||
RedactUser(hs).register(http_server)
|
||||
RedactUserStatus(hs).register(http_server)
|
||||
UserInvitesCount(hs).register(http_server)
|
||||
UserJoinedRoomCount(hs).register(http_server)
|
||||
|
||||
DeviceRestServlet(hs).register(http_server)
|
||||
DevicesRestServlet(hs).register(http_server)
|
||||
@@ -332,8 +336,7 @@ def register_servlets(hs: "HomeServer", http_server: HttpServer) -> None:
|
||||
BackgroundUpdateRestServlet(hs).register(http_server)
|
||||
BackgroundUpdateStartJobRestServlet(hs).register(http_server)
|
||||
ExperimentalFeaturesRestServlet(hs).register(http_server)
|
||||
if hs.config.experimental.msc3823_account_suspension:
|
||||
SuspendAccountRestServlet(hs).register(http_server)
|
||||
SuspendAccountRestServlet(hs).register(http_server)
|
||||
|
||||
|
||||
def register_servlets_for_client_rest_resource(
|
||||
|
||||
@@ -50,8 +50,10 @@ class EventReportsRestServlet(RestServlet):
|
||||
The parameters `from` and `limit` are required only for pagination.
|
||||
By default, a `limit` of 100 is used.
|
||||
The parameter `dir` can be used to define the order of results.
|
||||
The parameter `user_id` can be used to filter by user id.
|
||||
The parameter `room_id` can be used to filter by room id.
|
||||
The `user_id` query parameter filters by the user ID of the reporter of the event.
|
||||
The `room_id` query parameter filters by room id.
|
||||
The `event_sender_user_id` query parameter can be used to filter by the user id
|
||||
of the sender of the reported event.
|
||||
Returns:
|
||||
A list of reported events and an integer representing the total number of
|
||||
reported events that exist given this query
|
||||
@@ -71,6 +73,7 @@ class EventReportsRestServlet(RestServlet):
|
||||
direction = parse_enum(request, "dir", Direction, Direction.BACKWARDS)
|
||||
user_id = parse_string(request, "user_id")
|
||||
room_id = parse_string(request, "room_id")
|
||||
event_sender_user_id = parse_string(request, "event_sender_user_id")
|
||||
|
||||
if start < 0:
|
||||
raise SynapseError(
|
||||
@@ -87,7 +90,7 @@ class EventReportsRestServlet(RestServlet):
|
||||
)
|
||||
|
||||
event_reports, total = await self._store.get_event_reports_paginate(
|
||||
start, limit, direction, user_id, room_id
|
||||
start, limit, direction, user_id, room_id, event_sender_user_id
|
||||
)
|
||||
ret = {"event_reports": event_reports, "total": total}
|
||||
if (start + limit) < total:
|
||||
|
||||
@@ -983,7 +983,7 @@ class UserAdminServlet(RestServlet):
|
||||
|
||||
class UserMembershipRestServlet(RestServlet):
|
||||
"""
|
||||
Get room list of an user.
|
||||
Get list of joined room ID's for a user.
|
||||
"""
|
||||
|
||||
PATTERNS = admin_patterns("/users/(?P<user_id>[^/]*)/joined_rooms$")
|
||||
@@ -999,8 +999,9 @@ class UserMembershipRestServlet(RestServlet):
|
||||
await assert_requester_is_admin(self.auth, request)
|
||||
|
||||
room_ids = await self.store.get_rooms_for_user(user_id)
|
||||
ret = {"joined_rooms": list(room_ids), "total": len(room_ids)}
|
||||
return HTTPStatus.OK, ret
|
||||
rooms_response = {"joined_rooms": list(room_ids), "total": len(room_ids)}
|
||||
|
||||
return HTTPStatus.OK, rooms_response
|
||||
|
||||
|
||||
class PushersRestServlet(RestServlet):
|
||||
@@ -1501,3 +1502,50 @@ class RedactUserStatus(RestServlet):
|
||||
}
|
||||
else:
|
||||
raise NotFoundError("redact id '%s' not found" % redact_id)
|
||||
|
||||
|
||||
class UserInvitesCount(RestServlet):
|
||||
"""
|
||||
Return the count of invites that the user has sent after the given timestamp
|
||||
"""
|
||||
|
||||
PATTERNS = admin_patterns("/users/(?P<user_id>[^/]*)/sent_invite_count")
|
||||
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
self._auth = hs.get_auth()
|
||||
self.store = hs.get_datastores().main
|
||||
|
||||
async def on_GET(
|
||||
self, request: SynapseRequest, user_id: str
|
||||
) -> Tuple[int, JsonDict]:
|
||||
await assert_requester_is_admin(self._auth, request)
|
||||
from_ts = parse_integer(request, "from_ts", required=True)
|
||||
|
||||
sent_invite_count = await self.store.get_sent_invite_count_by_user(
|
||||
user_id, from_ts
|
||||
)
|
||||
|
||||
return HTTPStatus.OK, {"invite_count": sent_invite_count}
|
||||
|
||||
|
||||
class UserJoinedRoomCount(RestServlet):
|
||||
"""
|
||||
Return the count of rooms that the user has joined at or after the given timestamp, even
|
||||
if they have subsequently left/been banned from those rooms.
|
||||
"""
|
||||
|
||||
PATTERNS = admin_patterns("/users/(?P<user_id>[^/]*)/cumulative_joined_room_count")
|
||||
|
||||
def __init__(self, hs: "HomeServer"):
|
||||
self._auth = hs.get_auth()
|
||||
self.store = hs.get_datastores().main
|
||||
|
||||
async def on_GET(
|
||||
self, request: SynapseRequest, user_id: str
|
||||
) -> Tuple[int, JsonDict]:
|
||||
await assert_requester_is_admin(self._auth, request)
|
||||
from_ts = parse_integer(request, "from_ts", required=True)
|
||||
|
||||
joined_rooms = await self.store.get_rooms_for_user_by_date(user_id, from_ts)
|
||||
|
||||
return HTTPStatus.OK, {"cumulative_joined_room_count": len(joined_rooms)}
|
||||
|
||||
@@ -783,9 +783,9 @@ class RoomMessageListRestServlet(RestServlet):
|
||||
# decorator on `get_number_joined_users_in_room` doesn't play well with
|
||||
# the type system. Maybe in the future, it can use some ParamSpec
|
||||
# wizardry to fix it up.
|
||||
room_member_count_deferred = run_in_background( # type: ignore[call-arg]
|
||||
room_member_count_deferred = run_in_background( # type: ignore[call-overload]
|
||||
self.store.get_number_joined_users_in_room,
|
||||
room_id, # type: ignore[arg-type]
|
||||
room_id,
|
||||
)
|
||||
|
||||
requester = await self.auth.get_user_by_req(request, allow_guest=True)
|
||||
|
||||
@@ -339,6 +339,16 @@ class EventsWorkerStore(SQLBaseStore):
|
||||
writers=["master"],
|
||||
)
|
||||
|
||||
# Added to accommodate some queries for the admin API in order to fetch/filter
|
||||
# membership events by when it was received
|
||||
self.db_pool.updates.register_background_index_update(
|
||||
update_name="events_received_ts_index",
|
||||
index_name="received_ts_idx",
|
||||
table="events",
|
||||
columns=("received_ts",),
|
||||
where_clause="type = 'm.room.member'",
|
||||
)
|
||||
|
||||
def get_un_partial_stated_events_token(self, instance_name: str) -> int:
|
||||
return (
|
||||
self._un_partial_stated_events_stream_id_gen.get_current_token_for_writer(
|
||||
@@ -2589,6 +2599,44 @@ class EventsWorkerStore(SQLBaseStore):
|
||||
)
|
||||
)
|
||||
|
||||
async def get_sent_invite_count_by_user(self, user_id: str, from_ts: int) -> int:
|
||||
"""
|
||||
Get the number of invites sent by the given user at or after the provided timestamp.
|
||||
|
||||
Args:
|
||||
user_id: user ID to search against
|
||||
from_ts: a timestamp in milliseconds from the unix epoch. Filters against
|
||||
`events.received_ts`
|
||||
|
||||
"""
|
||||
|
||||
def _get_sent_invite_count_by_user_txn(
|
||||
txn: LoggingTransaction, user_id: str, from_ts: int
|
||||
) -> int:
|
||||
sql = """
|
||||
SELECT COUNT(rm.event_id)
|
||||
FROM room_memberships AS rm
|
||||
INNER JOIN events AS e USING(event_id)
|
||||
WHERE rm.sender = ?
|
||||
AND rm.membership = 'invite'
|
||||
AND e.type = 'm.room.member'
|
||||
AND e.received_ts >= ?
|
||||
"""
|
||||
|
||||
txn.execute(sql, (user_id, from_ts))
|
||||
res = txn.fetchone()
|
||||
|
||||
if res is None:
|
||||
return 0
|
||||
return int(res[0])
|
||||
|
||||
return await self.db_pool.runInteraction(
|
||||
"_get_sent_invite_count_by_user_txn",
|
||||
_get_sent_invite_count_by_user_txn,
|
||||
user_id,
|
||||
from_ts,
|
||||
)
|
||||
|
||||
@cached(tree=True)
|
||||
async def get_metadata_for_event(
|
||||
self, room_id: str, event_id: str
|
||||
|
||||
@@ -376,6 +376,11 @@ class PurgeEventsStore(StateGroupWorkerStore, CacheInvalidationWorkerStore):
|
||||
(room_id,),
|
||||
)
|
||||
|
||||
if isinstance(self.database_engine, PostgresEngine):
|
||||
# Disable statement timeouts for this transaction; purging rooms can
|
||||
# take a while!
|
||||
txn.execute("SET LOCAL statement_timeout = 0")
|
||||
|
||||
# First, fetch all the state groups that should be deleted, before
|
||||
# we delete that information.
|
||||
txn.execute(
|
||||
|
||||
@@ -1586,6 +1586,7 @@ class RoomWorkerStore(CacheInvalidationWorkerStore):
|
||||
direction: Direction = Direction.BACKWARDS,
|
||||
user_id: Optional[str] = None,
|
||||
room_id: Optional[str] = None,
|
||||
event_sender_user_id: Optional[str] = None,
|
||||
) -> Tuple[List[Dict[str, Any]], int]:
|
||||
"""Retrieve a paginated list of event reports
|
||||
|
||||
@@ -1596,6 +1597,8 @@ class RoomWorkerStore(CacheInvalidationWorkerStore):
|
||||
oldest first (forwards)
|
||||
user_id: search for user_id. Ignored if user_id is None
|
||||
room_id: search for room_id. Ignored if room_id is None
|
||||
event_sender_user_id: search for the sender of the reported event. Ignored if
|
||||
event_sender_user_id is None
|
||||
Returns:
|
||||
Tuple of:
|
||||
json list of event reports
|
||||
@@ -1615,6 +1618,10 @@ class RoomWorkerStore(CacheInvalidationWorkerStore):
|
||||
filters.append("er.room_id LIKE ?")
|
||||
args.extend(["%" + room_id + "%"])
|
||||
|
||||
if event_sender_user_id:
|
||||
filters.append("events.sender = ?")
|
||||
args.extend([event_sender_user_id])
|
||||
|
||||
if direction == Direction.BACKWARDS:
|
||||
order = "DESC"
|
||||
else:
|
||||
@@ -1630,6 +1637,7 @@ class RoomWorkerStore(CacheInvalidationWorkerStore):
|
||||
sql = """
|
||||
SELECT COUNT(*) as total_event_reports
|
||||
FROM event_reports AS er
|
||||
LEFT JOIN events USING(event_id)
|
||||
JOIN room_stats_state ON room_stats_state.room_id = er.room_id
|
||||
{}
|
||||
""".format(where_clause)
|
||||
@@ -1648,8 +1656,7 @@ class RoomWorkerStore(CacheInvalidationWorkerStore):
|
||||
room_stats_state.canonical_alias,
|
||||
room_stats_state.name
|
||||
FROM event_reports AS er
|
||||
LEFT JOIN events
|
||||
ON events.event_id = er.event_id
|
||||
LEFT JOIN events USING(event_id)
|
||||
JOIN room_stats_state
|
||||
ON room_stats_state.room_id = er.room_id
|
||||
{where_clause}
|
||||
|
||||
@@ -1572,6 +1572,40 @@ class RoomMemberWorkerStore(EventsWorkerStore, CacheInvalidationWorkerStore):
|
||||
get_sliding_sync_room_for_user_batch_txn,
|
||||
)
|
||||
|
||||
async def get_rooms_for_user_by_date(
|
||||
self, user_id: str, from_ts: int
|
||||
) -> FrozenSet[str]:
|
||||
"""
|
||||
Fetch a list of rooms that the user has joined at or after the given timestamp, including
|
||||
those they subsequently have left/been banned from.
|
||||
|
||||
Args:
|
||||
user_id: user ID of the user to search for
|
||||
from_ts: a timestamp in ms from the unix epoch at which to begin the search at
|
||||
"""
|
||||
|
||||
def _get_rooms_for_user_by_join_date_txn(
|
||||
txn: LoggingTransaction, user_id: str, timestamp: int
|
||||
) -> frozenset:
|
||||
sql = """
|
||||
SELECT rm.room_id
|
||||
FROM room_memberships AS rm
|
||||
INNER JOIN events AS e USING (event_id)
|
||||
WHERE rm.user_id = ?
|
||||
AND rm.membership = 'join'
|
||||
AND e.type = 'm.room.member'
|
||||
AND e.received_ts >= ?
|
||||
"""
|
||||
txn.execute(sql, (user_id, timestamp))
|
||||
return frozenset([r[0] for r in txn])
|
||||
|
||||
return await self.db_pool.runInteraction(
|
||||
"_get_rooms_for_user_by_join_date_txn",
|
||||
_get_rooms_for_user_by_join_date_txn,
|
||||
user_id,
|
||||
from_ts,
|
||||
)
|
||||
|
||||
|
||||
class RoomMemberBackgroundUpdateStore(SQLBaseStore):
|
||||
def __init__(
|
||||
|
||||
@@ -99,8 +99,8 @@ class PostgresEngine(
|
||||
allow_unsafe_locale = self.config.get("allow_unsafe_locale", False)
|
||||
|
||||
# Are we on a supported PostgreSQL version?
|
||||
if not allow_outdated_version and self._version < 110000:
|
||||
raise RuntimeError("Synapse requires PostgreSQL 11 or above.")
|
||||
if not allow_outdated_version and self._version < 130000:
|
||||
raise RuntimeError("Synapse requires PostgreSQL 13 or above.")
|
||||
|
||||
with db_conn.cursor() as txn:
|
||||
txn.execute("SHOW SERVER_ENCODING")
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
--
|
||||
-- This file is licensed under the Affero General Public License (AGPL) version 3.
|
||||
--
|
||||
-- Copyright (C) 2024 New Vector, Ltd
|
||||
--
|
||||
-- This program is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU Affero General Public License as
|
||||
-- published by the Free Software Foundation, either version 3 of the
|
||||
-- License, or (at your option) any later version.
|
||||
--
|
||||
-- See the GNU Affero General Public License for more details:
|
||||
-- <https://www.gnu.org/licenses/agpl-3.0.html>.
|
||||
|
||||
-- Add an index on `events.received_ts` for `m.room.member` events to allow for
|
||||
-- efficient lookup of events by timestamp in some Admin API's
|
||||
INSERT INTO background_updates (ordering, update_name, progress_json) VALUES
|
||||
(8806, 'events_received_ts_index', '{}');
|
||||
@@ -162,7 +162,7 @@ def _check_yield_points(
|
||||
d = result.throwExceptionIntoGenerator(gen)
|
||||
else:
|
||||
d = gen.send(result)
|
||||
except (StopIteration, defer._DefGen_Return) as e:
|
||||
except StopIteration as e:
|
||||
if current_context() != expected_context:
|
||||
# This happens when the context is lost sometime *after* the
|
||||
# final yield and returning. E.g. we forgot to yield on a
|
||||
@@ -183,7 +183,7 @@ def _check_yield_points(
|
||||
)
|
||||
)
|
||||
changes.append(err)
|
||||
# The `StopIteration` or `_DefGen_Return` contains the return value from the
|
||||
# The `StopIteration` contains the return value from the
|
||||
# generator.
|
||||
return cast(T, e.value)
|
||||
|
||||
|
||||
@@ -46,33 +46,43 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class TaskScheduler:
|
||||
"""
|
||||
This is a simple task sheduler aimed at resumable tasks: usually we use `run_in_background`
|
||||
to launch a background task, or Twisted `deferLater` if we want to do so later on.
|
||||
This is a simple task scheduler designed for resumable tasks. Normally,
|
||||
you'd use `run_in_background` to start a background task or Twisted's
|
||||
`deferLater` if you want to run it later.
|
||||
|
||||
The problem with that is that the tasks will just stop and never be resumed if synapse
|
||||
is stopped for whatever reason.
|
||||
The issue is that these tasks stop completely and won't resume if Synapse is
|
||||
shut down for any reason.
|
||||
|
||||
How this works:
|
||||
- A function mapped to a named action should first be registered with `register_action`.
|
||||
This function will be called when trying to resuming tasks after a synapse shutdown,
|
||||
so this registration should happen when synapse is initialised, NOT right before scheduling
|
||||
a task.
|
||||
- A task can then be launched using this named action with `schedule_task`. A `params` dict
|
||||
can be passed, and it will be available to the registered function when launched. This task
|
||||
can be launch either now-ish, or later on by giving a `timestamp` parameter.
|
||||
Here's how it works:
|
||||
|
||||
The function may call `update_task` at any time to update the `result` of the task,
|
||||
and this can be used to resume the task at a specific point and/or to convey a result to
|
||||
the code launching the task.
|
||||
You can also specify the `result` (and/or an `error`) when returning from the function.
|
||||
- Register an Action: First, you need to register a function to a named
|
||||
action using `register_action`. This function will be called to resume tasks
|
||||
after a Synapse shutdown. Make sure to register it when Synapse initializes,
|
||||
not right before scheduling the task.
|
||||
|
||||
The reconciliation loop runs every minute, so this is not a precise scheduler.
|
||||
There is a limit of 10 concurrent tasks, so tasks may be delayed if the pool is already
|
||||
full. In this regard, please take great care that scheduled tasks can actually finished.
|
||||
For now there is no mechanism to stop a running task if it is stuck.
|
||||
- Schedule a Task: You can launch a task linked to the named action
|
||||
using `schedule_task`. You can pass a `params` dictionary, which will be
|
||||
passed to the registered function when it's executed. Tasks can be scheduled
|
||||
to run either immediately or later by specifying a `timestamp`.
|
||||
|
||||
Tasks will be run on the worker specified with `run_background_tasks_on` config,
|
||||
or the main one by default.
|
||||
- Update Task: The function handling the task can call `update_task` at
|
||||
any point to update the task's `result`. This lets you resume the task from
|
||||
a specific point or pass results back to the code that scheduled it. When
|
||||
the function completes, you can also return a `result` or an `error`.
|
||||
|
||||
Things to keep in mind:
|
||||
|
||||
- The reconciliation loop runs every minute, so this is not a high-precision
|
||||
scheduler.
|
||||
|
||||
- Only 10 tasks can run at the same time. If the pool is full, tasks may be
|
||||
delayed. Make sure your scheduled tasks can actually finish.
|
||||
|
||||
- Currently, there's no way to stop a task if it gets stuck.
|
||||
|
||||
- Tasks will run on the worker defined by the `run_background_tasks_on`
|
||||
setting in your configuration. If no worker is specified, they'll run on
|
||||
the main one by default.
|
||||
"""
|
||||
|
||||
# Precision of the scheduler, evaluation of tasks to run will only happen
|
||||
@@ -157,7 +167,7 @@ class TaskScheduler:
|
||||
params: Optional[JsonMapping] = None,
|
||||
) -> str:
|
||||
"""Schedule a new potentially resumable task. A function matching the specified
|
||||
`action` should have be registered with `register_action` before the task is run.
|
||||
`action` should've been registered with `register_action` before the task is run.
|
||||
|
||||
Args:
|
||||
action: the name of a previously registered action
|
||||
@@ -210,15 +220,15 @@ class TaskScheduler:
|
||||
result: Optional[JsonMapping] = None,
|
||||
error: Optional[str] = None,
|
||||
) -> bool:
|
||||
"""Update some task associated values. This is exposed publicly so it can
|
||||
be used inside task functions, mainly to update the result and be able to
|
||||
resume a task at a specific step after a restart of synapse.
|
||||
"""Update some task-associated values. This is exposed publicly so it can
|
||||
be used inside task functions, mainly to update the result or resume
|
||||
a task at a specific step after a restart of synapse.
|
||||
|
||||
It can also be used to stage a task, by setting the `status` to `SCHEDULED` with
|
||||
a new timestamp.
|
||||
|
||||
The `status` can only be set to `ACTIVE` or `SCHEDULED`, `COMPLETE` and `FAILED`
|
||||
are terminal status and can only be set by returning it in the function.
|
||||
The `status` can only be set to `ACTIVE` or `SCHEDULED`. `COMPLETE` and `FAILED`
|
||||
are terminal statuses and can only be set by returning them from the function.
|
||||
|
||||
Args:
|
||||
id: the id of the task to update
|
||||
@@ -226,6 +236,12 @@ class TaskScheduler:
|
||||
status: the new `TaskStatus` of the task
|
||||
result: the new result of the task
|
||||
error: the new error of the task
|
||||
|
||||
Returns:
|
||||
True if the update was successful, False otherwise.
|
||||
|
||||
Raises:
|
||||
Exception: If a status other than `ACTIVE` or `SCHEDULED` was passed.
|
||||
"""
|
||||
if status == TaskStatus.COMPLETE or status == TaskStatus.FAILED:
|
||||
raise Exception(
|
||||
@@ -263,9 +279,9 @@ class TaskScheduler:
|
||||
max_timestamp: Optional[int] = None,
|
||||
limit: Optional[int] = None,
|
||||
) -> List[ScheduledTask]:
|
||||
"""Get a list of tasks. Returns all the tasks if no args is provided.
|
||||
"""Get a list of tasks. Returns all the tasks if no args are provided.
|
||||
|
||||
If an arg is `None` all tasks matching the other args will be selected.
|
||||
If an arg is `None`, all tasks matching the other args will be selected.
|
||||
If an arg is an empty list, the corresponding value of the task needs
|
||||
to be `None` to be selected.
|
||||
|
||||
@@ -277,8 +293,8 @@ class TaskScheduler:
|
||||
a timestamp inferior to the specified one
|
||||
limit: Only return `limit` number of rows if set.
|
||||
|
||||
Returns
|
||||
A list of `ScheduledTask`, ordered by increasing timestamps
|
||||
Returns:
|
||||
A list of `ScheduledTask`, ordered by increasing timestamps.
|
||||
"""
|
||||
return await self._store.get_scheduled_tasks(
|
||||
actions=actions,
|
||||
|
||||
@@ -39,7 +39,7 @@ except ImportError:
|
||||
|
||||
class ConfigLoadingFileTestCase(ConfigFileTestCase):
|
||||
def test_load_fails_if_server_name_missing(self) -> None:
|
||||
self.generate_config_and_remove_lines_containing("server_name")
|
||||
self.generate_config_and_remove_lines_containing(["server_name"])
|
||||
with self.assertRaises(ConfigError):
|
||||
HomeServerConfig.load_config("", ["-c", self.config_file])
|
||||
with self.assertRaises(ConfigError):
|
||||
@@ -76,7 +76,7 @@ class ConfigLoadingFileTestCase(ConfigFileTestCase):
|
||||
)
|
||||
|
||||
def test_load_succeeds_if_macaroon_secret_key_missing(self) -> None:
|
||||
self.generate_config_and_remove_lines_containing("macaroon")
|
||||
self.generate_config_and_remove_lines_containing(["macaroon"])
|
||||
config1 = HomeServerConfig.load_config("", ["-c", self.config_file])
|
||||
config2 = HomeServerConfig.load_config("", ["-c", self.config_file])
|
||||
config3 = HomeServerConfig.load_or_generate_config("", ["-c", self.config_file])
|
||||
@@ -111,7 +111,7 @@ class ConfigLoadingFileTestCase(ConfigFileTestCase):
|
||||
self.assertTrue(config3.registration.enable_registration)
|
||||
|
||||
def test_stats_enabled(self) -> None:
|
||||
self.generate_config_and_remove_lines_containing("enable_metrics")
|
||||
self.generate_config_and_remove_lines_containing(["enable_metrics"])
|
||||
self.add_lines_to_config(["enable_metrics: true"])
|
||||
|
||||
# The default Metrics Flags are off by default.
|
||||
@@ -131,6 +131,7 @@ class ConfigLoadingFileTestCase(ConfigFileTestCase):
|
||||
[
|
||||
"turn_shared_secret_path: /does/not/exist",
|
||||
"registration_shared_secret_path: /does/not/exist",
|
||||
"macaroon_secret_key_path: /does/not/exist",
|
||||
*["redis:\n enabled: true\n password_path: /does/not/exist"]
|
||||
* (hiredis is not None),
|
||||
]
|
||||
@@ -146,16 +147,20 @@ class ConfigLoadingFileTestCase(ConfigFileTestCase):
|
||||
[
|
||||
(
|
||||
"turn_shared_secret_path: {}",
|
||||
lambda c: c.voip.turn_shared_secret,
|
||||
lambda c: c.voip.turn_shared_secret.encode("utf-8"),
|
||||
),
|
||||
(
|
||||
"registration_shared_secret_path: {}",
|
||||
lambda c: c.registration.registration_shared_secret,
|
||||
lambda c: c.registration.registration_shared_secret.encode("utf-8"),
|
||||
),
|
||||
(
|
||||
"macaroon_secret_key_path: {}",
|
||||
lambda c: c.key.macaroon_secret_key,
|
||||
),
|
||||
*[
|
||||
(
|
||||
"redis:\n enabled: true\n password_path: {}",
|
||||
lambda c: c.redis.redis_password,
|
||||
lambda c: c.redis.redis_password.encode("utf-8"),
|
||||
)
|
||||
]
|
||||
* (hiredis is not None),
|
||||
@@ -164,11 +169,13 @@ class ConfigLoadingFileTestCase(ConfigFileTestCase):
|
||||
def test_secret_files_existing(
|
||||
self, config_line: str, get_secret: Callable[[RootConfig], str]
|
||||
) -> None:
|
||||
self.generate_config_and_remove_lines_containing("registration_shared_secret")
|
||||
self.generate_config_and_remove_lines_containing(
|
||||
["registration_shared_secret", "macaroon_secret_key"]
|
||||
)
|
||||
with tempfile.NamedTemporaryFile(buffering=0) as secret_file:
|
||||
secret_file.write(b"53C237")
|
||||
|
||||
self.add_lines_to_config(["", config_line.format(secret_file.name)])
|
||||
config = HomeServerConfig.load_config("", ["-c", self.config_file])
|
||||
|
||||
self.assertEqual(get_secret(config), "53C237")
|
||||
self.assertEqual(get_secret(config), b"53C237")
|
||||
|
||||
@@ -51,12 +51,13 @@ class ConfigFileTestCase(unittest.TestCase):
|
||||
],
|
||||
)
|
||||
|
||||
def generate_config_and_remove_lines_containing(self, needle: str) -> None:
|
||||
def generate_config_and_remove_lines_containing(self, needles: list[str]) -> None:
|
||||
self.generate_config()
|
||||
|
||||
with open(self.config_file) as f:
|
||||
contents = f.readlines()
|
||||
contents = [line for line in contents if needle not in line]
|
||||
for needle in needles:
|
||||
contents = [line for line in contents if needle not in line]
|
||||
with open(self.config_file, "w") as f:
|
||||
f.write("".join(contents))
|
||||
|
||||
|
||||
@@ -163,6 +163,7 @@ class SendEmailHandlerTestCaseIPv4(HomeserverTestCase):
|
||||
"email": {
|
||||
"notif_from": "noreply@test",
|
||||
"force_tls": True,
|
||||
"tlsname": "example.org",
|
||||
},
|
||||
}
|
||||
)
|
||||
@@ -186,10 +187,9 @@ class SendEmailHandlerTestCaseIPv4(HomeserverTestCase):
|
||||
self.assertEqual(host, self.reactor.lookups["localhost"])
|
||||
self.assertEqual(port, 465)
|
||||
# We need to make sure that TLS is happenning
|
||||
self.assertIsInstance(
|
||||
client_factory._wrappedFactory._testingContextFactory,
|
||||
ClientTLSOptions,
|
||||
)
|
||||
context_factory = client_factory._wrappedFactory._testingContextFactory
|
||||
self.assertIsInstance(context_factory, ClientTLSOptions)
|
||||
self.assertEqual(context_factory._hostname, "example.org") # tlsname
|
||||
# And since we use endpoints, they go through reactor.connectTCP
|
||||
# which works differently to connectSSL on the testing reactor
|
||||
|
||||
|
||||
@@ -796,6 +796,7 @@ class UserDirectoryTestCase(unittest.HomeserverTestCase):
|
||||
s = self.get_success(self.handler.search_users(u1, "user2", 10))
|
||||
self.assertEqual(len(s["results"]), 1)
|
||||
|
||||
# Kept old spam checker without `requester_id` tests for backwards compatibility.
|
||||
async def allow_all(user_profile: UserProfile) -> bool:
|
||||
# Allow all users.
|
||||
return False
|
||||
@@ -809,6 +810,7 @@ class UserDirectoryTestCase(unittest.HomeserverTestCase):
|
||||
s = self.get_success(self.handler.search_users(u1, "user2", 10))
|
||||
self.assertEqual(len(s["results"]), 1)
|
||||
|
||||
# Kept old spam checker without `requester_id` tests for backwards compatibility.
|
||||
# Configure a spam checker that filters all users.
|
||||
async def block_all(user_profile: UserProfile) -> bool:
|
||||
# All users are spammy.
|
||||
@@ -820,6 +822,40 @@ class UserDirectoryTestCase(unittest.HomeserverTestCase):
|
||||
s = self.get_success(self.handler.search_users(u1, "user2", 10))
|
||||
self.assertEqual(len(s["results"]), 0)
|
||||
|
||||
async def allow_all_expects_requester_id(
|
||||
user_profile: UserProfile, requester_id: str
|
||||
) -> bool:
|
||||
self.assertEqual(requester_id, u1)
|
||||
# Allow all users.
|
||||
return False
|
||||
|
||||
# Configure a spam checker that does not filter any users.
|
||||
spam_checker = self.hs.get_module_api_callbacks().spam_checker
|
||||
spam_checker._check_username_for_spam_callbacks = [
|
||||
allow_all_expects_requester_id
|
||||
]
|
||||
|
||||
# The results do not change:
|
||||
# We get one search result when searching for user2 by user1.
|
||||
s = self.get_success(self.handler.search_users(u1, "user2", 10))
|
||||
self.assertEqual(len(s["results"]), 1)
|
||||
|
||||
# Configure a spam checker that filters all users.
|
||||
async def block_all_expects_requester_id(
|
||||
user_profile: UserProfile, requester_id: str
|
||||
) -> bool:
|
||||
self.assertEqual(requester_id, u1)
|
||||
# All users are spammy.
|
||||
return True
|
||||
|
||||
spam_checker._check_username_for_spam_callbacks = [
|
||||
block_all_expects_requester_id
|
||||
]
|
||||
|
||||
# User1 now gets no search results for any of the other users.
|
||||
s = self.get_success(self.handler.search_users(u1, "user2", 10))
|
||||
self.assertEqual(len(s["results"]), 0)
|
||||
|
||||
@override_config(
|
||||
{
|
||||
"spam_checker": {
|
||||
|
||||
@@ -854,7 +854,7 @@ class MatrixFederationAgentTests(TestCase):
|
||||
def test_proxy_with_no_scheme(self) -> None:
|
||||
http_proxy_agent = ProxyAgent(self.reactor, use_proxy=True)
|
||||
proxy_ep = checked_cast(HostnameEndpoint, http_proxy_agent.http_proxy_endpoint)
|
||||
self.assertEqual(proxy_ep._hostStr, "proxy.com")
|
||||
self.assertEqual(proxy_ep._hostText, "proxy.com")
|
||||
self.assertEqual(proxy_ep._port, 8888)
|
||||
|
||||
@patch.dict(os.environ, {"http_proxy": "socks://proxy.com:8888"})
|
||||
@@ -866,14 +866,14 @@ class MatrixFederationAgentTests(TestCase):
|
||||
def test_proxy_with_http_scheme(self) -> None:
|
||||
http_proxy_agent = ProxyAgent(self.reactor, use_proxy=True)
|
||||
proxy_ep = checked_cast(HostnameEndpoint, http_proxy_agent.http_proxy_endpoint)
|
||||
self.assertEqual(proxy_ep._hostStr, "proxy.com")
|
||||
self.assertEqual(proxy_ep._hostText, "proxy.com")
|
||||
self.assertEqual(proxy_ep._port, 8888)
|
||||
|
||||
@patch.dict(os.environ, {"http_proxy": "https://proxy.com:8888"})
|
||||
def test_proxy_with_https_scheme(self) -> None:
|
||||
https_proxy_agent = ProxyAgent(self.reactor, use_proxy=True)
|
||||
proxy_ep = checked_cast(_WrapperEndpoint, https_proxy_agent.http_proxy_endpoint)
|
||||
self.assertEqual(proxy_ep._wrappedEndpoint._hostStr, "proxy.com")
|
||||
self.assertEqual(proxy_ep._wrappedEndpoint._hostText, "proxy.com")
|
||||
self.assertEqual(proxy_ep._wrappedEndpoint._port, 8888)
|
||||
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class CancellableReplicationEndpoint(ReplicationEndpoint):
|
||||
self.clock = hs.get_clock()
|
||||
|
||||
@staticmethod
|
||||
async def _serialize_payload() -> JsonDict:
|
||||
async def _serialize_payload(**kwargs: ReplicationEndpoint) -> JsonDict:
|
||||
return {}
|
||||
|
||||
@cancellable
|
||||
@@ -68,7 +68,7 @@ class UncancellableReplicationEndpoint(ReplicationEndpoint):
|
||||
self.clock = hs.get_clock()
|
||||
|
||||
@staticmethod
|
||||
async def _serialize_payload() -> JsonDict:
|
||||
async def _serialize_payload(**kwargs: ReplicationEndpoint) -> JsonDict:
|
||||
return {}
|
||||
|
||||
async def _handle_request( # type: ignore[override]
|
||||
|
||||
@@ -378,6 +378,41 @@ class EventReportsTestCase(unittest.HomeserverTestCase):
|
||||
self.assertEqual(len(channel.json_body["event_reports"]), 1)
|
||||
self.assertNotIn("next_token", channel.json_body)
|
||||
|
||||
def test_filter_against_event_sender(self) -> None:
|
||||
"""
|
||||
Tests filtering by the sender of the reported event
|
||||
"""
|
||||
# first grab all the reports
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
self.url,
|
||||
access_token=self.admin_user_tok,
|
||||
)
|
||||
self.assertEqual(channel.code, 200)
|
||||
|
||||
# filter out set of report ids of events sent by one of the users
|
||||
locally_filtered_report_ids = set()
|
||||
for event_report in channel.json_body["event_reports"]:
|
||||
if event_report["sender"] == self.other_user:
|
||||
locally_filtered_report_ids.add(event_report["id"])
|
||||
|
||||
# grab the report ids by sender and compare to filtered report ids
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
f"{self.url}?event_sender_user_id={self.other_user}",
|
||||
access_token=self.admin_user_tok,
|
||||
)
|
||||
self.assertEqual(200, channel.code)
|
||||
self.assertEqual(channel.json_body["total"], len(locally_filtered_report_ids))
|
||||
|
||||
event_reports = channel.json_body["event_reports"]
|
||||
server_filtered_report_ids = set()
|
||||
for event_report in event_reports:
|
||||
server_filtered_report_ids.add(event_report["id"])
|
||||
self.assertIncludes(
|
||||
locally_filtered_report_ids, server_filtered_report_ids, exact=True
|
||||
)
|
||||
|
||||
def _create_event_and_report(self, room_id: str, user_tok: str) -> None:
|
||||
"""Create and report events"""
|
||||
resp = self.helper.send(room_id, tok=user_tok)
|
||||
|
||||
@@ -5031,7 +5031,6 @@ class UserSuspensionTestCase(unittest.HomeserverTestCase):
|
||||
|
||||
self.store = hs.get_datastores().main
|
||||
|
||||
@override_config({"experimental_features": {"msc3823_account_suspension": True}})
|
||||
def test_suspend_user(self) -> None:
|
||||
# test that suspending user works
|
||||
channel = self.make_request(
|
||||
@@ -5503,3 +5502,254 @@ class UserRedactionBackgroundTaskTestCase(BaseMultiWorkerStreamTestCase):
|
||||
redaction_ids.add(event["redacts"])
|
||||
|
||||
self.assertIncludes(redaction_ids, original_event_ids, exact=True)
|
||||
|
||||
|
||||
class GetInvitesFromUserTestCase(unittest.HomeserverTestCase):
|
||||
servlets = [
|
||||
synapse.rest.admin.register_servlets,
|
||||
login.register_servlets,
|
||||
admin.register_servlets,
|
||||
room.register_servlets,
|
||||
]
|
||||
|
||||
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
|
||||
self.admin = self.register_user("thomas", "pass", True)
|
||||
self.admin_tok = self.login("thomas", "pass")
|
||||
|
||||
self.bad_user = self.register_user("teresa", "pass")
|
||||
self.bad_user_tok = self.login("teresa", "pass")
|
||||
|
||||
self.random_users = []
|
||||
for i in range(4):
|
||||
self.random_users.append(self.register_user(f"user{i}", f"pass{i}"))
|
||||
|
||||
self.room1 = self.helper.create_room_as(self.bad_user, tok=self.bad_user_tok)
|
||||
self.room2 = self.helper.create_room_as(self.bad_user, tok=self.bad_user_tok)
|
||||
self.room3 = self.helper.create_room_as(self.bad_user, tok=self.bad_user_tok)
|
||||
|
||||
@unittest.override_config(
|
||||
{"rc_invites": {"per_issuer": {"per_second": 1000, "burst_count": 1000}}}
|
||||
)
|
||||
def test_get_user_invite_count_new_invites_test_case(self) -> None:
|
||||
"""
|
||||
Test that new invites that arrive after a provided timestamp are counted
|
||||
"""
|
||||
# grab a current timestamp
|
||||
before_invites_sent_ts = self.hs.get_clock().time_msec()
|
||||
|
||||
# bad user sends some invites
|
||||
for room_id in [self.room1, self.room2]:
|
||||
for user in self.random_users:
|
||||
self.helper.invite(room_id, self.bad_user, user, tok=self.bad_user_tok)
|
||||
|
||||
# fetch using timestamp, all should be returned
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
f"/_synapse/admin/v1/users/{self.bad_user}/sent_invite_count?from_ts={before_invites_sent_ts}",
|
||||
access_token=self.admin_tok,
|
||||
)
|
||||
self.assertEqual(channel.code, 200)
|
||||
self.assertEqual(channel.json_body["invite_count"], 8)
|
||||
|
||||
# send some more invites, they should show up in addition to original 8 using same timestamp
|
||||
for user in self.random_users:
|
||||
self.helper.invite(
|
||||
self.room3, src=self.bad_user, targ=user, tok=self.bad_user_tok
|
||||
)
|
||||
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
f"/_synapse/admin/v1/users/{self.bad_user}/sent_invite_count?from_ts={before_invites_sent_ts}",
|
||||
access_token=self.admin_tok,
|
||||
)
|
||||
self.assertEqual(channel.code, 200)
|
||||
self.assertEqual(channel.json_body["invite_count"], 12)
|
||||
|
||||
def test_get_user_invite_count_invites_before_ts_test_case(self) -> None:
|
||||
"""
|
||||
Test that invites sent before provided ts are not counted
|
||||
"""
|
||||
# bad user sends some invites
|
||||
for room_id in [self.room1, self.room2]:
|
||||
for user in self.random_users:
|
||||
self.helper.invite(room_id, self.bad_user, user, tok=self.bad_user_tok)
|
||||
|
||||
# add a msec between last invite and ts
|
||||
after_invites_sent_ts = self.hs.get_clock().time_msec() + 1
|
||||
|
||||
# fetch invites with timestamp, none should be returned
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
f"/_synapse/admin/v1/users/{self.bad_user}/sent_invite_count?from_ts={after_invites_sent_ts}",
|
||||
access_token=self.admin_tok,
|
||||
)
|
||||
self.assertEqual(channel.code, 200)
|
||||
self.assertEqual(channel.json_body["invite_count"], 0)
|
||||
|
||||
def test_user_invite_count_kick_ban_not_counted(self) -> None:
|
||||
"""
|
||||
Test that kicks and bans are not counted in invite count
|
||||
"""
|
||||
to_kick_user_id = self.register_user("kick_me", "pass")
|
||||
to_kick_tok = self.login("kick_me", "pass")
|
||||
|
||||
self.helper.join(self.room1, to_kick_user_id, tok=to_kick_tok)
|
||||
|
||||
# grab a current timestamp
|
||||
before_invites_sent_ts = self.hs.get_clock().time_msec()
|
||||
|
||||
# bad user sends some invites (8)
|
||||
for room_id in [self.room1, self.room2]:
|
||||
for user in self.random_users:
|
||||
self.helper.invite(
|
||||
room_id, src=self.bad_user, targ=user, tok=self.bad_user_tok
|
||||
)
|
||||
|
||||
# fetch using timestamp, all invites sent should be counted
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
f"/_synapse/admin/v1/users/{self.bad_user}/sent_invite_count?from_ts={before_invites_sent_ts}",
|
||||
access_token=self.admin_tok,
|
||||
)
|
||||
self.assertEqual(channel.code, 200)
|
||||
self.assertEqual(channel.json_body["invite_count"], 8)
|
||||
|
||||
# send a kick and some bans and make sure these aren't counted against invite total
|
||||
for user in self.random_users:
|
||||
self.helper.ban(
|
||||
self.room1, src=self.bad_user, targ=user, tok=self.bad_user_tok
|
||||
)
|
||||
|
||||
channel = self.make_request(
|
||||
"POST",
|
||||
f"/_matrix/client/v3/rooms/{self.room1}/kick",
|
||||
content={"user_id": to_kick_user_id},
|
||||
access_token=self.bad_user_tok,
|
||||
)
|
||||
self.assertEqual(channel.code, 200)
|
||||
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
f"/_synapse/admin/v1/users/{self.bad_user}/sent_invite_count?from_ts={before_invites_sent_ts}",
|
||||
access_token=self.admin_tok,
|
||||
)
|
||||
self.assertEqual(channel.code, 200)
|
||||
self.assertEqual(channel.json_body["invite_count"], 8)
|
||||
|
||||
|
||||
class GetCumulativeJoinedRoomCountForUserTestCase(unittest.HomeserverTestCase):
|
||||
servlets = [
|
||||
synapse.rest.admin.register_servlets,
|
||||
login.register_servlets,
|
||||
admin.register_servlets,
|
||||
room.register_servlets,
|
||||
]
|
||||
|
||||
def prepare(self, reactor: MemoryReactor, clock: Clock, hs: HomeServer) -> None:
|
||||
self.admin = self.register_user("thomas", "pass", True)
|
||||
self.admin_tok = self.login("thomas", "pass")
|
||||
|
||||
self.bad_user = self.register_user("teresa", "pass")
|
||||
self.bad_user_tok = self.login("teresa", "pass")
|
||||
|
||||
def test_user_cumulative_joined_room_count(self) -> None:
|
||||
"""
|
||||
Tests proper count returned from /cumulative_joined_room_count endpoint
|
||||
"""
|
||||
# Create rooms and join, grab timestamp before room creation
|
||||
before_room_creation_timestamp = self.hs.get_clock().time_msec()
|
||||
|
||||
joined_rooms = []
|
||||
for _ in range(3):
|
||||
room = self.helper.create_room_as(self.admin, tok=self.admin_tok)
|
||||
self.helper.join(
|
||||
room, user=self.bad_user, expect_code=200, tok=self.bad_user_tok
|
||||
)
|
||||
joined_rooms.append(room)
|
||||
|
||||
# get a timestamp after room creation and join, add a msec between last join and ts
|
||||
after_room_creation = self.hs.get_clock().time_msec() + 1
|
||||
|
||||
# Get rooms using this timestamp, there should be none since all rooms were created and joined
|
||||
# before provided timestamp
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
f"/_synapse/admin/v1/users/{self.bad_user}/cumulative_joined_room_count?from_ts={int(after_room_creation)}",
|
||||
access_token=self.admin_tok,
|
||||
)
|
||||
self.assertEqual(200, channel.code, msg=channel.json_body)
|
||||
self.assertEqual(0, channel.json_body["cumulative_joined_room_count"])
|
||||
|
||||
# fetch rooms with the older timestamp before they were created and joined, this should
|
||||
# return the rooms
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
f"/_synapse/admin/v1/users/{self.bad_user}/cumulative_joined_room_count?from_ts={int(before_room_creation_timestamp)}",
|
||||
access_token=self.admin_tok,
|
||||
)
|
||||
self.assertEqual(200, channel.code, msg=channel.json_body)
|
||||
self.assertEqual(
|
||||
len(joined_rooms), channel.json_body["cumulative_joined_room_count"]
|
||||
)
|
||||
|
||||
def test_user_joined_room_count_includes_left_and_banned_rooms(self) -> None:
|
||||
"""
|
||||
Tests proper count returned from /joined_room_count endpoint when user has left
|
||||
or been banned from joined rooms
|
||||
"""
|
||||
# Create rooms and join, grab timestamp before room creation
|
||||
before_room_creation_timestamp = self.hs.get_clock().time_msec()
|
||||
|
||||
joined_rooms = []
|
||||
for _ in range(3):
|
||||
room = self.helper.create_room_as(self.admin, tok=self.admin_tok)
|
||||
self.helper.join(
|
||||
room, user=self.bad_user, expect_code=200, tok=self.bad_user_tok
|
||||
)
|
||||
joined_rooms.append(room)
|
||||
|
||||
# fetch rooms with the older timestamp before they were created and joined
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
f"/_synapse/admin/v1/users/{self.bad_user}/cumulative_joined_room_count?from_ts={int(before_room_creation_timestamp)}",
|
||||
access_token=self.admin_tok,
|
||||
)
|
||||
self.assertEqual(200, channel.code, msg=channel.json_body)
|
||||
self.assertEqual(
|
||||
len(joined_rooms), channel.json_body["cumulative_joined_room_count"]
|
||||
)
|
||||
|
||||
# have the user banned from/leave the joined rooms
|
||||
self.helper.ban(
|
||||
joined_rooms[0],
|
||||
src=self.admin,
|
||||
targ=self.bad_user,
|
||||
expect_code=200,
|
||||
tok=self.admin_tok,
|
||||
)
|
||||
self.helper.change_membership(
|
||||
joined_rooms[1],
|
||||
src=self.bad_user,
|
||||
targ=self.bad_user,
|
||||
membership="leave",
|
||||
expect_code=200,
|
||||
tok=self.bad_user_tok,
|
||||
)
|
||||
self.helper.ban(
|
||||
joined_rooms[2],
|
||||
src=self.admin,
|
||||
targ=self.bad_user,
|
||||
expect_code=200,
|
||||
tok=self.admin_tok,
|
||||
)
|
||||
|
||||
# fetch the joined room count again, the number should remain the same as the collected joined rooms
|
||||
channel = self.make_request(
|
||||
"GET",
|
||||
f"/_synapse/admin/v1/users/{self.bad_user}/cumulative_joined_room_count?from_ts={int(before_room_creation_timestamp)}",
|
||||
access_token=self.admin_tok,
|
||||
)
|
||||
self.assertEqual(200, channel.code, msg=channel.json_body)
|
||||
self.assertEqual(
|
||||
len(joined_rooms), channel.json_body["cumulative_joined_room_count"]
|
||||
)
|
||||
|
||||
@@ -1337,17 +1337,13 @@ class RoomJoinTestCase(RoomBase):
|
||||
"POST", f"/join/{self.room1}", access_token=self.tok2
|
||||
)
|
||||
self.assertEqual(channel.code, 403)
|
||||
self.assertEqual(
|
||||
channel.json_body["errcode"], "ORG.MATRIX.MSC3823.USER_ACCOUNT_SUSPENDED"
|
||||
)
|
||||
self.assertEqual(channel.json_body["errcode"], "M_USER_SUSPENDED")
|
||||
|
||||
channel = self.make_request(
|
||||
"POST", f"/rooms/{self.room1}/join", access_token=self.tok2
|
||||
)
|
||||
self.assertEqual(channel.code, 403)
|
||||
self.assertEqual(
|
||||
channel.json_body["errcode"], "ORG.MATRIX.MSC3823.USER_ACCOUNT_SUSPENDED"
|
||||
)
|
||||
self.assertEqual(channel.json_body["errcode"], "M_USER_SUSPENDED")
|
||||
|
||||
def test_suspended_user_cannot_knock_on_room(self) -> None:
|
||||
# set the user as suspended
|
||||
@@ -1361,9 +1357,7 @@ class RoomJoinTestCase(RoomBase):
|
||||
shorthand=False,
|
||||
)
|
||||
self.assertEqual(channel.code, 403)
|
||||
self.assertEqual(
|
||||
channel.json_body["errcode"], "ORG.MATRIX.MSC3823.USER_ACCOUNT_SUSPENDED"
|
||||
)
|
||||
self.assertEqual(channel.json_body["errcode"], "M_USER_SUSPENDED")
|
||||
|
||||
def test_suspended_user_cannot_invite_to_room(self) -> None:
|
||||
# set the user as suspended
|
||||
@@ -1376,9 +1370,7 @@ class RoomJoinTestCase(RoomBase):
|
||||
access_token=self.tok1,
|
||||
content={"user_id": self.user2},
|
||||
)
|
||||
self.assertEqual(
|
||||
channel.json_body["errcode"], "ORG.MATRIX.MSC3823.USER_ACCOUNT_SUSPENDED"
|
||||
)
|
||||
self.assertEqual(channel.json_body["errcode"], "M_USER_SUSPENDED")
|
||||
|
||||
|
||||
class RoomAppserviceTsParamTestCase(unittest.HomeserverTestCase):
|
||||
@@ -4011,9 +4003,7 @@ class UserSuspensionTests(unittest.HomeserverTestCase):
|
||||
access_token=self.tok1,
|
||||
content={"body": "hello", "msgtype": "m.text"},
|
||||
)
|
||||
self.assertEqual(
|
||||
channel.json_body["errcode"], "ORG.MATRIX.MSC3823.USER_ACCOUNT_SUSPENDED"
|
||||
)
|
||||
self.assertEqual(channel.json_body["errcode"], "M_USER_SUSPENDED")
|
||||
|
||||
def test_suspended_user_cannot_change_profile_data(self) -> None:
|
||||
# set the user as suspended
|
||||
@@ -4026,9 +4016,7 @@ class UserSuspensionTests(unittest.HomeserverTestCase):
|
||||
content={"avatar_url": "mxc://matrix.org/wefh34uihSDRGhw34"},
|
||||
shorthand=False,
|
||||
)
|
||||
self.assertEqual(
|
||||
channel.json_body["errcode"], "ORG.MATRIX.MSC3823.USER_ACCOUNT_SUSPENDED"
|
||||
)
|
||||
self.assertEqual(channel.json_body["errcode"], "M_USER_SUSPENDED")
|
||||
|
||||
channel2 = self.make_request(
|
||||
"PUT",
|
||||
@@ -4037,9 +4025,7 @@ class UserSuspensionTests(unittest.HomeserverTestCase):
|
||||
content={"displayname": "something offensive"},
|
||||
shorthand=False,
|
||||
)
|
||||
self.assertEqual(
|
||||
channel2.json_body["errcode"], "ORG.MATRIX.MSC3823.USER_ACCOUNT_SUSPENDED"
|
||||
)
|
||||
self.assertEqual(channel2.json_body["errcode"], "M_USER_SUSPENDED")
|
||||
|
||||
def test_suspended_user_cannot_redact_messages_other_than_their_own(self) -> None:
|
||||
# first user sends message
|
||||
@@ -4073,9 +4059,7 @@ class UserSuspensionTests(unittest.HomeserverTestCase):
|
||||
content={"reason": "bogus"},
|
||||
shorthand=False,
|
||||
)
|
||||
self.assertEqual(
|
||||
channel.json_body["errcode"], "ORG.MATRIX.MSC3823.USER_ACCOUNT_SUSPENDED"
|
||||
)
|
||||
self.assertEqual(channel.json_body["errcode"], "M_USER_SUSPENDED")
|
||||
|
||||
# but can redact their own
|
||||
channel = self.make_request(
|
||||
|
||||
Reference in New Issue
Block a user