Compare commits
232 commits
kb_develop
...
kb_lts
Author | SHA1 | Date | |
---|---|---|---|
|
8b6e55b8dc | ||
|
6a9212459f | ||
|
46fec44ecc | ||
|
898e89bfad | ||
|
c33fea88b2 | ||
|
7e47439787 | ||
|
245a74f9ca | ||
|
d2842db18d | ||
|
346c37df80 | ||
|
20f06798a0 | ||
|
e66aaee1a4 | ||
|
9bfbba3224 | ||
|
378af3a0a0 | ||
|
d096965eec | ||
|
2abaa9b68a | ||
|
df36f12d46 | ||
|
0e8f23ebee | ||
|
3c851f2d21 | ||
|
af63f9564c | ||
|
b6278e0d39 | ||
|
ec9644b9a6 | ||
|
4e94e72911 | ||
|
836c02d816 | ||
|
8bc6f3c73c | ||
|
f9a929ed5c | ||
|
d675803f07 | ||
|
a2d3de48f4 | ||
|
a53ec866d5 | ||
|
3ab66262de | ||
|
a02ff33f0e | ||
|
a652293842 | ||
|
63ad8254ff | ||
|
8fe1cefe4c | ||
|
86f15cef66 | ||
|
49820ecefa | ||
|
9d2e59bb45 | ||
|
6fcb1f5799 | ||
|
297ad9aeb8 | ||
|
e1be281e3d | ||
|
c06436eb91 | ||
|
d1854798c9 | ||
|
a2c7f7f690 | ||
|
6f2a3fa5d1 | ||
|
7b7d404efe | ||
|
0fc738a323 | ||
|
5cb36daa0f | ||
|
a8039dda13 | ||
|
0a345ad5e1 | ||
|
29c35ef4f9 | ||
|
13bab94265 | ||
|
161aa0f8f6 | ||
|
fe92b241b2 | ||
|
a5641a9244 | ||
|
31ad8c7fdf | ||
|
77b7b8caaa | ||
|
6896542a76 | ||
|
a5b4a2b7e7 | ||
|
d4bf22b632 | ||
|
4fb4721072 | ||
|
df974a912b | ||
|
6cd9bd6ae1 | ||
|
9b6219c48f | ||
|
88b2d6eca5 | ||
|
846f59c6e9 | ||
|
17f69c0002 | ||
|
1e87634a43 | ||
|
5fd7cd79e0 | ||
|
fcae9435ec | ||
|
55408f8085 | ||
|
3f75c6f048 | ||
|
bfc287fd6b | ||
|
19ed22dc58 | ||
|
520b2086af | ||
|
e925fd6802 | ||
|
75d7e4fbdd | ||
|
e511b02de5 | ||
|
441c8712e1 | ||
|
cc2bfe5188 | ||
|
c2a19f8a81 | ||
|
c93aacafde | ||
|
9740c7eaea | ||
|
8ab0ca7d64 | ||
|
7920aa59e8 | ||
|
943792c187 | ||
|
186f916192 | ||
|
f9c41ae43b | ||
|
b8edc95e8a | ||
|
16213a678d | ||
|
a8dd32102f | ||
|
6fc07ff31f | ||
|
997b021b69 | ||
|
2865bfadaf | ||
|
8c72e80019 | ||
|
8cf78825a2 | ||
|
67b2e62331 | ||
|
56b7d1a7b6 | ||
|
51ef619140 | ||
|
e69780ec59 | ||
|
c3be5a3d2e | ||
|
86807e4799 | ||
|
0143c9d3e1 | ||
|
ab3f9852f2 | ||
|
7af69f5cf5 | ||
|
f784213c64 | ||
|
6536d96d1b | ||
|
ed8e4bab4c | ||
|
6a5ea61928 | ||
|
c329c56625 | ||
|
6ba77a9feb | ||
|
631a037524 | ||
|
bdb6650ebc | ||
|
f3ad918950 | ||
|
9a7802655f | ||
|
328a9b8157 | ||
|
4fd22acb4a | ||
|
28b666b0d5 | ||
|
fbb07893b8 | ||
|
4fbaadaf1b | ||
|
c5d56de98d | ||
|
e8c1a13dd4 | ||
|
188d9bdbf6 | ||
|
f6588fe079 | ||
|
0e4e98fad1 | ||
|
15de520201 | ||
|
684f99908f | ||
|
55581c9335 | ||
|
7d3899d5c9 | ||
|
f0a1dc1f1e | ||
|
35f830b7ec | ||
|
d0cc051298 | ||
|
9190f53d7b | ||
|
d9b9e66bb5 | ||
|
e4ec4ce217 | ||
|
870ee80fd3 | ||
|
76a37bd040 | ||
|
7c8ca0c6d6 | ||
|
f1700523f1 | ||
|
0b0c7af2c1 | ||
|
1a33d348d0 | ||
|
6d43b63275 | ||
|
ae2dce813a | ||
|
b7230cd759 | ||
|
5274a399d6 | ||
|
3bae139748 | ||
|
088c8482ff | ||
|
0cdc6faa46 | ||
|
8a306337e5 | ||
|
a6641f828b | ||
|
58969aed33 | ||
|
ae71ed50e8 | ||
|
1d42b6b82f | ||
|
4633bb8ce0 | ||
|
1ab050eb52 | ||
|
4eb98ef755 | ||
|
7a22999f92 | ||
|
c5c464804d | ||
|
779237f054 | ||
|
b377f82b1d | ||
|
6fe2a47357 | ||
|
2dbf176d23 | ||
|
499bc716a5 | ||
|
3837ec2227 | ||
|
1998c561b2 | ||
|
c0a9db3611 | ||
|
01caa18e5b | ||
|
c609b726cb | ||
|
4d96d716c4 | ||
|
3ecc991f63 | ||
|
8f2dac0567 | ||
|
dfc8fcc6f0 | ||
|
e8c5754142 | ||
|
0a01bc01d2 | ||
|
a12b7551cf | ||
|
7abc61887f | ||
|
279be07679 | ||
|
11db9e44b1 | ||
|
eb280d93a9 | ||
|
49d561178d | ||
|
33f254be42 | ||
|
ad7f23556b | ||
|
b21c0458b6 | ||
|
4a5f0f9259 | ||
|
4f6d89f161 | ||
|
5ba8141df9 | ||
|
4b10bf23ab | ||
|
aa5e50e5d5 | ||
|
3e4bd83326 | ||
|
e17ddc1b6a | ||
|
7edb05337e | ||
|
267e9cbcc8 | ||
|
06123147d5 | ||
|
d7875adad2 | ||
|
e227885d0b | ||
|
38105dfbaa | ||
|
e8b6c16b52 | ||
|
e9b69478d1 | ||
|
0dec7b450b | ||
|
5cce294953 | ||
|
f0a8bad941 | ||
|
d79c88394b | ||
|
4f62dbdd64 | ||
|
90371a4fc4 | ||
|
71b60b09f4 | ||
|
2af8b653cb | ||
|
5863a7756e | ||
|
9310c1c81b | ||
|
4b8fe9df73 | ||
|
7b9496322f | ||
|
09115731d6 | ||
|
e11100d782 | ||
|
252ea2fc67 | ||
|
8d02e58ff4 | ||
|
1076a6cd62 | ||
|
54a07731d1 | ||
|
81d7cfd544 | ||
|
e6f4c91c5c | ||
|
de86e822f4 | ||
|
4c38706474 | ||
|
4fc2523546 | ||
|
d5bc10b711 | ||
|
c66ade7de8 | ||
|
bece853e3c | ||
|
700ae1f918 | ||
|
13205b54fd | ||
|
8be33d4316 | ||
|
cdedae6d63 | ||
|
aa69ca74ed | ||
|
156d32689b | ||
|
ef149674f0 | ||
|
129705db44 | ||
|
8383288219 | ||
|
eea2654236 |
272 changed files with 3781 additions and 1462 deletions
|
@ -1,4 +1,10 @@
|
||||||
---
|
---
|
||||||
ignore:
|
ignore:
|
||||||
# Sidekiq security issue, fixes in the latest Sidekiq 7 but we can not upgrade. Will be fixed in Sidekiq 6.5.10
|
# devise-two-factor advisory about brute-forcing TOTP
|
||||||
- CVE-2023-26141
|
# We have rate-limits on authentication endpoints in place (including second
|
||||||
|
# factor verification) since Mastodon v3.2.0
|
||||||
|
- CVE-2024-0227
|
||||||
|
# devise-two-factor advisory about generated secrets being weaker than expected
|
||||||
|
# We call `generate_otp_secret` ourselves with a requested length of 32 characters,
|
||||||
|
# which exceeds the recommended remediation of 26 characters, so we're safe
|
||||||
|
- CVE-2024-8796
|
||||||
|
|
|
@ -130,6 +130,7 @@ Naming/VariableNumber:
|
||||||
- 'db/migrate/20190820003045_update_statuses_index.rb'
|
- 'db/migrate/20190820003045_update_statuses_index.rb'
|
||||||
- 'db/migrate/20190823221802_add_local_index_to_statuses.rb'
|
- 'db/migrate/20190823221802_add_local_index_to_statuses.rb'
|
||||||
- 'db/migrate/20200119112504_add_public_index_to_statuses.rb'
|
- 'db/migrate/20200119112504_add_public_index_to_statuses.rb'
|
||||||
|
- 'db/migrate/20231212225737_improve_index_for_public_timeline_speed.rb'
|
||||||
- 'spec/models/account_spec.rb'
|
- 'spec/models/account_spec.rb'
|
||||||
- 'spec/models/domain_block_spec.rb'
|
- 'spec/models/domain_block_spec.rb'
|
||||||
- 'spec/models/user_spec.rb'
|
- 'spec/models/user_spec.rb'
|
||||||
|
@ -441,6 +442,7 @@ Rails/SkipsModelValidations:
|
||||||
- 'db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb'
|
- 'db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb'
|
||||||
- 'db/migrate/20191007013357_update_pt_locales.rb'
|
- 'db/migrate/20191007013357_update_pt_locales.rb'
|
||||||
- 'db/migrate/20220316233212_update_kurdish_locales.rb'
|
- 'db/migrate/20220316233212_update_kurdish_locales.rb'
|
||||||
|
- 'db/migrate/20240109035435_remove_hidden_anonymous_from_domain_blocks.rb'
|
||||||
- 'db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb'
|
- 'db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb'
|
||||||
- 'db/post_migrate/20200917193528_migrate_notifications_type.rb'
|
- 'db/post_migrate/20200917193528_migrate_notifications_type.rb'
|
||||||
- 'db/post_migrate/20201017234926_fill_account_suspension_origin.rb'
|
- 'db/post_migrate/20201017234926_fill_account_suspension_origin.rb'
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
3.2.2
|
3.2.3
|
||||||
|
|
BIN
.yarn/install-state.gz
Normal file
BIN
.yarn/install-state.gz
Normal file
Binary file not shown.
241
CHANGELOG.md
241
CHANGELOG.md
|
@ -2,6 +2,247 @@
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [4.2.13] - 2024-09-30
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Fix ReDoS vulnerability on some Ruby versions ([GHSA-jpxp-r43f-rhvx](https://github.com/mastodon/mastodon/security/advisories/GHSA-jpxp-r43f-rhvx))
|
||||||
|
- Update dependencies
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add “A Mastodon update is available.” message on admin dashboard for non-bugfix updates (#32106 by @ClearlyClaire)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Change Mastodon to issue correct HTTP signatures by default (#31994 by @ClearlyClaire)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix replies collection being cached improperly
|
||||||
|
- Fix security context sometimes not being added in LD-Signed activities (#31871 by @ClearlyClaire)
|
||||||
|
- Fix error when encountering reblog of deleted post in feed rebuild (#32001 by @ClearlyClaire)
|
||||||
|
|
||||||
|
## [4.2.12] - 2024-08-19
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix broken notifications for mentions from local moderators ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/31484))
|
||||||
|
|
||||||
|
## [4.2.11] - 2024-08-16
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add support for incoming `<s>` tag ([mediaformat](https://github.com/mastodon/mastodon/pull/31375))
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Change logic of block/mute bypass for mentions from moderators to only apply to visible roles with moderation powers ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/31271))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix incorrect rate limit on PUT requests ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/31356))
|
||||||
|
- Fix presence of `ß` in adjacent word preventing mention and hashtag matching ([adamniedzielski](https://github.com/mastodon/mastodon/pull/31122))
|
||||||
|
- Fix processing of webfinger responses with multiple `self` links ([adamniedzielski](https://github.com/mastodon/mastodon/pull/31110))
|
||||||
|
- Fix duplicate `orderedItems` in user archive's `outbox.json` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/31099))
|
||||||
|
- Fix click event handling when clicking outside of an open dropdown menu ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/31251))
|
||||||
|
- Fix status processing failing halfway when a remote post has a malformed `replies` attribute ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/31246))
|
||||||
|
- Fix `--verbose` option of `tootctl media remove`, which was previously erroneously removed ([mjankowski](https://github.com/mastodon/mastodon/pull/30536))
|
||||||
|
- Fix division by zero on some video/GIF files ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30600))
|
||||||
|
- Fix Web UI trying to save user settings despite being logged out ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30324))
|
||||||
|
- Fix hashtag regexp matching some link anchors ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30190))
|
||||||
|
- Fix local account search on LDAP login being case-sensitive ([raucao](https://github.com/mastodon/mastodon/pull/30113))
|
||||||
|
- Fix development environment admin account not being auto-approved ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29958))
|
||||||
|
- Fix report reason selector in moderation interface not unselecting rules when changing category ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29026))
|
||||||
|
- Fix already-invalid reports failing to resolve ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29027))
|
||||||
|
- Fix OCR when using S3/CDN for assets ([vmstan](https://github.com/mastodon/mastodon/pull/28551))
|
||||||
|
- Fix error when encountering malformed `Tag` objects from Kbin ([ShadowJonathan](https://github.com/mastodon/mastodon/pull/28235))
|
||||||
|
- Fix not all allowed image formats showing in file picker when uploading custom emoji ([june128](https://github.com/mastodon/mastodon/pull/28076))
|
||||||
|
- Fix search popout listing unusable search options when logged out ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27918))
|
||||||
|
- Fix processing of featured collections lacking an `items` attribute ([tribela](https://github.com/mastodon/mastodon/pull/27581))
|
||||||
|
- Fix `mastodon:stats` decoration of stats rake task ([mjankowski](https://github.com/mastodon/mastodon/pull/31104))
|
||||||
|
|
||||||
|
## [4.2.10] - 2024-07-04
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Fix incorrect permission checking on multiple API endpoints ([GHSA-58x8-3qxw-6hm7](https://github.com/mastodon/mastodon/security/advisories/GHSA-58x8-3qxw-6hm7))
|
||||||
|
- Fix incorrect authorship checking when processing some activities (CVE-2024-37903, [GHSA-xjvf-fm67-4qc3](https://github.com/mastodon/mastodon/security/advisories/GHSA-xjvf-fm67-4qc3))
|
||||||
|
- Fix ongoing streaming sessions not being invalidated when application tokens get revoked ([GHSA-vp5r-5pgw-jwqx](https://github.com/mastodon/mastodon/security/advisories/GHSA-vp5r-5pgw-jwqx))
|
||||||
|
- Update dependencies
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add yarn version specification to avoid confusion with Yarn 3 and Yarn 4
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Change preview cards generation to skip unusually long URLs ([oneiros](https://github.com/mastodon/mastodon/pull/30854))
|
||||||
|
- Change search modifiers to be case-insensitive ([Gargron](https://github.com/mastodon/mastodon/pull/30865))
|
||||||
|
- Change `STATSD_ADDR` handling to emit a warning rather than crashing if the address is unreachable ([timothyjrogers](https://github.com/mastodon/mastodon/pull/30691))
|
||||||
|
- Change PWA start URL from `/home` to `/` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27377))
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Removed dependency on `posix-spawn` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18559))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix scheduled statuses scheduled in less than 5 minutes being immediately published ([danielmbrasil](https://github.com/mastodon/mastodon/pull/30584))
|
||||||
|
- Fix encoding detection for link cards ([oneiros](https://github.com/mastodon/mastodon/pull/30780))
|
||||||
|
- Fix `/admin/accounts/:account_id/statuses/:id` for edited posts with media attachments ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30819))
|
||||||
|
- Fix duplicate `@context` attribute in user archive export ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30653))
|
||||||
|
|
||||||
|
## [4.2.9] - 2024-05-30
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Update dependencies
|
||||||
|
- Fix private mention filtering ([GHSA-5fq7-3p3j-9vrf](https://github.com/mastodon/mastodon/security/advisories/GHSA-5fq7-3p3j-9vrf))
|
||||||
|
- Fix password change endpoint not being rate-limited ([GHSA-q3rg-xx5v-4mxh](https://github.com/mastodon/mastodon/security/advisories/GHSA-q3rg-xx5v-4mxh))
|
||||||
|
- Add hardening around rate-limit bypass ([GHSA-c2r5-cfqr-c553](https://github.com/mastodon/mastodon/security/advisories/GHSA-c2r5-cfqr-c553))
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add rate-limit on OAuth application registration ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/30316))
|
||||||
|
- Add fallback redirection when getting a webfinger query `WEB_DOMAIN@WEB_DOMAIN` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28592))
|
||||||
|
- Add `digest` attribute to `Admin::DomainBlock` entity in REST API ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/29092))
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Remove superfluous application-level caching in some controllers ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29862))
|
||||||
|
- Remove aggressive OAuth application vacuuming ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/30316))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix leaking Elasticsearch connections in Sidekiq processes ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30450))
|
||||||
|
- Fix language of remote posts not being recognized when using unusual casing ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30403))
|
||||||
|
- Fix off-by-one in `tootctl media` commands ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30306))
|
||||||
|
- Fix removal of allowed domains (in `LIMITED_FEDERATION_MODE`) not being recorded in the audit log ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/30125))
|
||||||
|
- Fix not being able to block a subdomain of an already-blocked domain through the API ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30119))
|
||||||
|
- Fix `Idempotency-Key` being ignored when scheduling a post ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/30084))
|
||||||
|
- Fix crash when supplying the `FFMPEG_BINARY` environment variable ([timothyjrogers](https://github.com/mastodon/mastodon/pull/30022))
|
||||||
|
- Fix improper email address validation ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29838))
|
||||||
|
- Fix results/query in `api/v1/featured_tags/suggestions` ([mjankowski](https://github.com/mastodon/mastodon/pull/29597))
|
||||||
|
- Fix unblocking internationalized domain names under certain conditions ([tribela](https://github.com/mastodon/mastodon/pull/29530))
|
||||||
|
- Fix admin account created by `mastodon:setup` not being auto-approved ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29379))
|
||||||
|
- Fix reference to non-existent var in CLI maintenance command ([mjankowski](https://github.com/mastodon/mastodon/pull/28363))
|
||||||
|
|
||||||
|
## [4.2.8] - 2024-02-23
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add hourly task to automatically require approval for new registrations in the absence of moderators ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29318), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/29355))
|
||||||
|
In order to prevent future abandoned Mastodon servers from being used for spam, harassment and other malicious activity, Mastodon will now automatically switch new user registrations to require moderator approval whenever they are left open and no activity (including non-moderation actions from apps) from any logged-in user with permission to access moderation reports has been detected in a full week.
|
||||||
|
When this happens, users with the permission to change server settings will receive an email notification.
|
||||||
|
This feature is disabled when `EMAIL_DOMAIN_ALLOWLIST` is used, and can also be disabled with `DISABLE_AUTOMATIC_SWITCHING_TO_APPROVED_REGISTRATIONS=true`.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Change registrations to be closed by default on new installations ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29280))
|
||||||
|
If you are running a server and never changed your registrations mode from the default, updating will automatically close your registrations.
|
||||||
|
Simply re-enable them through the administration interface or using `tootctl settings registrations open` if you want to enable them again.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix processing of remote ActivityPub actors making use of `Link` objects as `Image` `url` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29335))
|
||||||
|
- Fix link verifications when page size exceeds 1MB ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29358))
|
||||||
|
|
||||||
|
## [4.2.7] - 2024-02-16
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix OmniAuth tests and edge cases in error handling ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/29201), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/29207))
|
||||||
|
- Fix new installs by upgrading to the latest release of the `nsa` gem, instead of a no longer existing commit ([mjankowski](https://github.com/mastodon/mastodon/pull/29065))
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Fix insufficient checking of remote posts ([GHSA-jhrq-qvrm-qr36](https://github.com/mastodon/mastodon/security/advisories/GHSA-jhrq-qvrm-qr36))
|
||||||
|
|
||||||
|
## [4.2.6] - 2024-02-14
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Update the `sidekiq-unique-jobs` dependency (see [GHSA-cmh9-rx85-xj38](https://github.com/mhenrixon/sidekiq-unique-jobs/security/advisories/GHSA-cmh9-rx85-xj38))
|
||||||
|
In addition, we have disabled the web interface for `sidekiq-unique-jobs` out of caution.
|
||||||
|
If you need it, you can re-enable it by setting `ENABLE_SIDEKIQ_UNIQUE_JOBS_UI=true`.
|
||||||
|
If you only need to clear all locks, you can now use `bundle exec rake sidekiq_unique_jobs:delete_all_locks`.
|
||||||
|
- Update the `nokogiri` dependency (see [GHSA-xc9x-jj77-9p9j](https://github.com/sparklemotion/nokogiri/security/advisories/GHSA-xc9x-jj77-9p9j))
|
||||||
|
- Disable administrative Doorkeeper routes ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/29187))
|
||||||
|
- Fix ongoing streaming sessions not being invalidated when applications get deleted in some cases ([GHSA-7w3c-p9j8-mq3x](https://github.com/mastodon/mastodon/security/advisories/GHSA-7w3c-p9j8-mq3x))
|
||||||
|
In some rare cases, the streaming server was not notified of access tokens revocation on application deletion.
|
||||||
|
- Change external authentication behavior to never reattach a new identity to an existing user by default ([GHSA-vm39-j3vx-pch3](https://github.com/mastodon/mastodon/security/advisories/GHSA-vm39-j3vx-pch3))
|
||||||
|
Up until now, Mastodon has allowed new identities from external authentication providers to attach to an existing local user based on their verified e-mail address.
|
||||||
|
This allowed upgrading users from a database-stored password to an external authentication provider, or move from one authentication provider to another.
|
||||||
|
However, this behavior may be unexpected, and means that when multiple authentication providers are configured, the overall security would be that of the least secure authentication provider.
|
||||||
|
For these reasons, this behavior is now locked under the `ALLOW_UNSAFE_AUTH_PROVIDER_REATTACH` environment variable.
|
||||||
|
In addition, regardless of this environment variable, Mastodon will refuse to attach two identities from the same authentication provider to the same account.
|
||||||
|
|
||||||
|
## [4.2.5] - 2024-02-01
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Fix insufficient origin validation (CVE-2024-23832, [GHSA-3fjr-858r-92rw](https://github.com/mastodon/mastodon/security/advisories/GHSA-3fjr-858r-92rw))
|
||||||
|
|
||||||
|
## [4.2.4] - 2024-01-24
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix error when processing remote files with unusually long names ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28823))
|
||||||
|
- Fix processing of compacted single-item JSON-LD collections ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28816))
|
||||||
|
- Retry 401 errors on replies fetching ([ShadowJonathan](https://github.com/mastodon/mastodon/pull/28788))
|
||||||
|
- Fix `RecordNotUnique` errors in LinkCrawlWorker ([tribela](https://github.com/mastodon/mastodon/pull/28748))
|
||||||
|
- Fix Mastodon not correctly processing HTTP Signatures with query strings ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28443), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/28476))
|
||||||
|
- Fix potential redirection loop of streaming endpoint ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28665))
|
||||||
|
- Fix streaming API redirection ignoring the port of `streaming_api_base_url` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28558))
|
||||||
|
- Fix error when processing link preview with an array as `inLanguage` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28252))
|
||||||
|
- Fix unsupported time zone or locale preventing sign-up ([Gargron](https://github.com/mastodon/mastodon/pull/28035))
|
||||||
|
- Fix "Hide these posts from home" list setting not refreshing when switching lists ([brianholley](https://github.com/mastodon/mastodon/pull/27763))
|
||||||
|
- Fix missing background behind dismissable banner in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/27479))
|
||||||
|
- Fix line wrapping of language selection button with long locale codes ([gunchleoc](https://github.com/mastodon/mastodon/pull/27100), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27127))
|
||||||
|
- Fix `Undo Announce` activity not being sent to non-follower authors ([MitarashiDango](https://github.com/mastodon/mastodon/pull/18482))
|
||||||
|
- Fix N+1s because of association preloaders not actually getting called ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28339))
|
||||||
|
- Fix empty column explainer getting cropped under certain conditions ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28337))
|
||||||
|
- Fix `LinkCrawlWorker` error when encountering empty OEmbed response ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28268))
|
||||||
|
- Fix call to inefficient `delete_matched` cache method in domain blocks ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28367))
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Add rate-limit of TOTP authentication attempts at controller level ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/28801))
|
||||||
|
|
||||||
|
## [4.2.3] - 2023-12-05
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix dependency on `json-canonicalization` version that has been made unavailable since last release
|
||||||
|
|
||||||
|
## [4.2.2] - 2023-12-04
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Change dismissed banners to be stored server-side ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27055))
|
||||||
|
- Change GIF max matrix size error to explicitly mention GIF files ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27927))
|
||||||
|
- Change `Follow` activities delivery to bypass availability check ([ShadowJonathan](https://github.com/mastodon/mastodon/pull/27586))
|
||||||
|
- Change single-column navigation notice to be displayed outside of the logo container ([renchap](https://github.com/mastodon/mastodon/pull/27462), [renchap](https://github.com/mastodon/mastodon/pull/27476))
|
||||||
|
- Change Content-Security-Policy to be tighter on media paths ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26889))
|
||||||
|
- Change post language code to include country code when relevant ([gunchleoc](https://github.com/mastodon/mastodon/pull/27099), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27207))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix upper border radius of onboarding columns ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27890))
|
||||||
|
- Fix incoming status creation date not being restricted to standard ISO8601 ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27655), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/28081))
|
||||||
|
- Fix some posts from threads received out-of-order sometimes not being inserted into timelines ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27653))
|
||||||
|
- Fix posts from force-sensitized accounts being able to trend ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27620))
|
||||||
|
- Fix error when trying to delete already-deleted file with OpenStack Swift ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27569))
|
||||||
|
- Fix batch attachment deletion when using OpenStack Swift ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27554))
|
||||||
|
- Fix processing LDSigned activities from actors with unknown public keys ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27474))
|
||||||
|
- Fix error and incorrect URLs in `/api/v1/accounts/:id/featured_tags` for remote accounts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27459))
|
||||||
|
- Fix report processing notice not mentioning the report number when performing a custom action ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27442))
|
||||||
|
- Fix handling of `inLanguage` attribute in preview card processing ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27423))
|
||||||
|
- Fix own posts being removed from home timeline when unfollowing a used hashtag ([kmycode](https://github.com/mastodon/mastodon/pull/27391))
|
||||||
|
- Fix some link anchors being recognized as hashtags ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27271), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/27584))
|
||||||
|
- Fix format-dependent redirects being cached regardless of requested format ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/27634))
|
||||||
|
|
||||||
## [4.2.1] - 2023-10-10
|
## [4.2.1] - 2023-10-10
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# This needs to be bookworm-slim because the Ruby image is built on bookworm-slim
|
# This needs to be bookworm-slim because the Ruby image is built on bookworm-slim
|
||||||
ARG NODE_VERSION="20.6-bookworm-slim"
|
ARG NODE_VERSION="20.6-bookworm-slim"
|
||||||
|
|
||||||
FROM ghcr.io/moritzheiber/ruby-jemalloc:3.2.2-slim as ruby
|
FROM ghcr.io/moritzheiber/ruby-jemalloc:3.2.3-slim as ruby
|
||||||
FROM node:${NODE_VERSION} as build
|
FROM node:${NODE_VERSION} as build
|
||||||
|
|
||||||
COPY --link --from=ruby /opt/ruby /opt/ruby
|
COPY --link --from=ruby /opt/ruby /opt/ruby
|
||||||
|
|
5
Gemfile
5
Gemfile
|
@ -61,11 +61,10 @@ gem 'kaminari', '~> 1.2'
|
||||||
gem 'link_header', '~> 0.0'
|
gem 'link_header', '~> 0.0'
|
||||||
gem 'mime-types', '~> 3.5.0', require: 'mime/types/columnar'
|
gem 'mime-types', '~> 3.5.0', require: 'mime/types/columnar'
|
||||||
gem 'nokogiri', '~> 1.15'
|
gem 'nokogiri', '~> 1.15'
|
||||||
gem 'nsa', github: 'jhawthorn/nsa', ref: 'e020fcc3a54d993ab45b7194d89ab720296c111b'
|
gem 'nsa'
|
||||||
gem 'oj', '~> 3.14'
|
gem 'oj', '~> 3.14'
|
||||||
gem 'ox', '~> 2.14'
|
gem 'ox', '~> 2.14'
|
||||||
gem 'parslet'
|
gem 'parslet'
|
||||||
gem 'posix-spawn'
|
|
||||||
gem 'public_suffix', '~> 5.0'
|
gem 'public_suffix', '~> 5.0'
|
||||||
gem 'pundit', '~> 2.3'
|
gem 'pundit', '~> 2.3'
|
||||||
gem 'premailer-rails'
|
gem 'premailer-rails'
|
||||||
|
@ -206,3 +205,5 @@ gem 'net-http', '~> 0.3.2'
|
||||||
gem 'rubyzip', '~> 2.3'
|
gem 'rubyzip', '~> 2.3'
|
||||||
|
|
||||||
gem 'hcaptcha', '~> 7.1'
|
gem 'hcaptcha', '~> 7.1'
|
||||||
|
|
||||||
|
gem 'mail', '~> 2.8'
|
||||||
|
|
209
Gemfile.lock
209
Gemfile.lock
|
@ -7,17 +7,6 @@ GIT
|
||||||
hkdf (~> 0.2)
|
hkdf (~> 0.2)
|
||||||
jwt (~> 2.0)
|
jwt (~> 2.0)
|
||||||
|
|
||||||
GIT
|
|
||||||
remote: https://github.com/jhawthorn/nsa.git
|
|
||||||
revision: e020fcc3a54d993ab45b7194d89ab720296c111b
|
|
||||||
ref: e020fcc3a54d993ab45b7194d89ab720296c111b
|
|
||||||
specs:
|
|
||||||
nsa (0.2.8)
|
|
||||||
activesupport (>= 4.2, < 7.2)
|
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
||||||
sidekiq (>= 3.5)
|
|
||||||
statsd-ruby (~> 1.4, >= 1.4.0)
|
|
||||||
|
|
||||||
GIT
|
GIT
|
||||||
remote: https://github.com/mastodon/rails-settings-cached.git
|
remote: https://github.com/mastodon/rails-settings-cached.git
|
||||||
revision: 86328ef0bd04ce21cc0504ff5e334591e8c2ccab
|
revision: 86328ef0bd04ce21cc0504ff5e334591e8c2ccab
|
||||||
|
@ -39,47 +28,47 @@ GIT
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actioncable (7.0.8)
|
actioncable (7.0.8.4)
|
||||||
actionpack (= 7.0.8)
|
actionpack (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8)
|
activesupport (= 7.0.8.4)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (>= 0.6.1)
|
websocket-driver (>= 0.6.1)
|
||||||
actionmailbox (7.0.8)
|
actionmailbox (7.0.8.4)
|
||||||
actionpack (= 7.0.8)
|
actionpack (= 7.0.8.4)
|
||||||
activejob (= 7.0.8)
|
activejob (= 7.0.8.4)
|
||||||
activerecord (= 7.0.8)
|
activerecord (= 7.0.8.4)
|
||||||
activestorage (= 7.0.8)
|
activestorage (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8)
|
activesupport (= 7.0.8.4)
|
||||||
mail (>= 2.7.1)
|
mail (>= 2.7.1)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
actionmailer (7.0.8)
|
actionmailer (7.0.8.4)
|
||||||
actionpack (= 7.0.8)
|
actionpack (= 7.0.8.4)
|
||||||
actionview (= 7.0.8)
|
actionview (= 7.0.8.4)
|
||||||
activejob (= 7.0.8)
|
activejob (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8)
|
activesupport (= 7.0.8.4)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
actionpack (7.0.8)
|
actionpack (7.0.8.4)
|
||||||
actionview (= 7.0.8)
|
actionview (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8)
|
activesupport (= 7.0.8.4)
|
||||||
rack (~> 2.0, >= 2.2.4)
|
rack (~> 2.0, >= 2.2.4)
|
||||||
rack-test (>= 0.6.3)
|
rack-test (>= 0.6.3)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||||
actiontext (7.0.8)
|
actiontext (7.0.8.4)
|
||||||
actionpack (= 7.0.8)
|
actionpack (= 7.0.8.4)
|
||||||
activerecord (= 7.0.8)
|
activerecord (= 7.0.8.4)
|
||||||
activestorage (= 7.0.8)
|
activestorage (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8)
|
activesupport (= 7.0.8.4)
|
||||||
globalid (>= 0.6.0)
|
globalid (>= 0.6.0)
|
||||||
nokogiri (>= 1.8.5)
|
nokogiri (>= 1.8.5)
|
||||||
actionview (7.0.8)
|
actionview (7.0.8.4)
|
||||||
activesupport (= 7.0.8)
|
activesupport (= 7.0.8.4)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.4)
|
erubi (~> 1.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
|
@ -89,22 +78,22 @@ GEM
|
||||||
activemodel (>= 4.1, < 7.1)
|
activemodel (>= 4.1, < 7.1)
|
||||||
case_transform (>= 0.2)
|
case_transform (>= 0.2)
|
||||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||||
activejob (7.0.8)
|
activejob (7.0.8.4)
|
||||||
activesupport (= 7.0.8)
|
activesupport (= 7.0.8.4)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (7.0.8)
|
activemodel (7.0.8.4)
|
||||||
activesupport (= 7.0.8)
|
activesupport (= 7.0.8.4)
|
||||||
activerecord (7.0.8)
|
activerecord (7.0.8.4)
|
||||||
activemodel (= 7.0.8)
|
activemodel (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8)
|
activesupport (= 7.0.8.4)
|
||||||
activestorage (7.0.8)
|
activestorage (7.0.8.4)
|
||||||
actionpack (= 7.0.8)
|
actionpack (= 7.0.8.4)
|
||||||
activejob (= 7.0.8)
|
activejob (= 7.0.8.4)
|
||||||
activerecord (= 7.0.8)
|
activerecord (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8)
|
activesupport (= 7.0.8.4)
|
||||||
marcel (~> 1.0)
|
marcel (~> 1.0)
|
||||||
mini_mime (>= 1.1.0)
|
mini_mime (>= 1.1.0)
|
||||||
activesupport (7.0.8)
|
activesupport (7.0.8.4)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
|
@ -148,6 +137,7 @@ GEM
|
||||||
net-http-persistent (~> 4.0)
|
net-http-persistent (~> 4.0)
|
||||||
nokogiri (~> 1, >= 1.10.8)
|
nokogiri (~> 1, >= 1.10.8)
|
||||||
base64 (0.1.1)
|
base64 (0.1.1)
|
||||||
|
bcp47_spec (0.2.1)
|
||||||
bcrypt (3.1.18)
|
bcrypt (3.1.18)
|
||||||
better_errors (2.10.1)
|
better_errors (2.10.1)
|
||||||
erubi (>= 1.0.0)
|
erubi (>= 1.0.0)
|
||||||
|
@ -201,8 +191,8 @@ GEM
|
||||||
xpath (~> 3.2)
|
xpath (~> 3.2)
|
||||||
case_transform (0.2)
|
case_transform (0.2)
|
||||||
activesupport
|
activesupport
|
||||||
cbor (0.5.9.6)
|
cbor (0.5.9.8)
|
||||||
charlock_holmes (0.7.7)
|
charlock_holmes (0.7.8)
|
||||||
chewy (7.3.4)
|
chewy (7.3.4)
|
||||||
activesupport (>= 5.2)
|
activesupport (>= 5.2)
|
||||||
elasticsearch (>= 7.12.0, < 7.14.0)
|
elasticsearch (>= 7.12.0, < 7.14.0)
|
||||||
|
@ -211,7 +201,7 @@ GEM
|
||||||
climate_control (0.2.0)
|
climate_control (0.2.0)
|
||||||
cocoon (1.2.15)
|
cocoon (1.2.15)
|
||||||
color_diff (0.1)
|
color_diff (0.1)
|
||||||
concurrent-ruby (1.2.2)
|
concurrent-ruby (1.3.4)
|
||||||
connection_pool (2.4.1)
|
connection_pool (2.4.1)
|
||||||
cose (1.3.0)
|
cose (1.3.0)
|
||||||
cbor (~> 0.5.9)
|
cbor (~> 0.5.9)
|
||||||
|
@ -225,7 +215,7 @@ GEM
|
||||||
activerecord (>= 5.a)
|
activerecord (>= 5.a)
|
||||||
database_cleaner-core (~> 2.0.0)
|
database_cleaner-core (~> 2.0.0)
|
||||||
database_cleaner-core (2.0.1)
|
database_cleaner-core (2.0.1)
|
||||||
date (3.3.3)
|
date (3.3.4)
|
||||||
debug_inspector (1.1.0)
|
debug_inspector (1.1.0)
|
||||||
devise (4.9.2)
|
devise (4.9.2)
|
||||||
bcrypt (~> 3.0)
|
bcrypt (~> 3.0)
|
||||||
|
@ -266,7 +256,7 @@ GEM
|
||||||
multi_json
|
multi_json
|
||||||
encryptor (3.0.0)
|
encryptor (3.0.0)
|
||||||
erubi (1.12.0)
|
erubi (1.12.0)
|
||||||
et-orbi (1.2.7)
|
et-orbi (1.2.11)
|
||||||
tzinfo
|
tzinfo
|
||||||
excon (0.100.0)
|
excon (0.100.0)
|
||||||
fabrication (2.30.0)
|
fabrication (2.30.0)
|
||||||
|
@ -298,7 +288,7 @@ GEM
|
||||||
faraday_middleware (1.2.0)
|
faraday_middleware (1.2.0)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
fast_blank (1.0.1)
|
fast_blank (1.0.1)
|
||||||
fastimage (2.2.7)
|
fastimage (2.3.1)
|
||||||
ffi (1.15.5)
|
ffi (1.15.5)
|
||||||
ffi-compiler (1.0.1)
|
ffi-compiler (1.0.1)
|
||||||
ffi (>= 1.0.0)
|
ffi (>= 1.0.0)
|
||||||
|
@ -316,8 +306,8 @@ GEM
|
||||||
fog-json (>= 1.0)
|
fog-json (>= 1.0)
|
||||||
ipaddress (>= 0.8)
|
ipaddress (>= 0.8)
|
||||||
formatador (0.3.0)
|
formatador (0.3.0)
|
||||||
fugit (1.8.1)
|
fugit (1.11.1)
|
||||||
et-orbi (~> 1, >= 1.2.7)
|
et-orbi (~> 1, >= 1.2.11)
|
||||||
raabro (~> 1.4)
|
raabro (~> 1.4)
|
||||||
fuubar (2.5.1)
|
fuubar (2.5.1)
|
||||||
rspec-core (~> 3.0)
|
rspec-core (~> 3.0)
|
||||||
|
@ -360,7 +350,7 @@ GEM
|
||||||
httplog (1.6.2)
|
httplog (1.6.2)
|
||||||
rack (>= 2.0)
|
rack (>= 2.0)
|
||||||
rainbow (>= 2.0.0)
|
rainbow (>= 2.0.0)
|
||||||
i18n (1.14.1)
|
i18n (1.14.5)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
i18n-tasks (1.0.12)
|
i18n-tasks (1.0.12)
|
||||||
activesupport (>= 4.0.2)
|
activesupport (>= 4.0.2)
|
||||||
|
@ -377,19 +367,19 @@ GEM
|
||||||
ipaddress (0.8.3)
|
ipaddress (0.8.3)
|
||||||
jmespath (1.6.2)
|
jmespath (1.6.2)
|
||||||
json (2.6.3)
|
json (2.6.3)
|
||||||
json-canonicalization (0.3.2)
|
json-canonicalization (1.0.0)
|
||||||
json-jwt (1.15.3)
|
json-jwt (1.15.3.1)
|
||||||
activesupport (>= 4.2)
|
activesupport (>= 4.2)
|
||||||
aes_key_wrap
|
aes_key_wrap
|
||||||
bindata
|
bindata
|
||||||
httpclient
|
httpclient
|
||||||
json-ld (3.2.5)
|
json-ld (3.3.1)
|
||||||
htmlentities (~> 4.3)
|
htmlentities (~> 4.3)
|
||||||
json-canonicalization (~> 0.3, >= 0.3.2)
|
json-canonicalization (~> 1.0)
|
||||||
link_header (~> 0.0, >= 0.0.8)
|
link_header (~> 0.0, >= 0.0.8)
|
||||||
multi_json (~> 1.15)
|
multi_json (~> 1.15)
|
||||||
rack (>= 2.2, < 4)
|
rack (>= 2.2, < 4)
|
||||||
rdf (~> 3.2, >= 3.2.10)
|
rdf (~> 3.3)
|
||||||
json-ld-preloaded (3.2.2)
|
json-ld-preloaded (3.2.2)
|
||||||
json-ld (~> 3.2)
|
json-ld (~> 3.2)
|
||||||
rdf (~> 3.2)
|
rdf (~> 3.2)
|
||||||
|
@ -434,7 +424,7 @@ GEM
|
||||||
activesupport (>= 4)
|
activesupport (>= 4)
|
||||||
railties (>= 4)
|
railties (>= 4)
|
||||||
request_store (~> 1.0)
|
request_store (~> 1.0)
|
||||||
loofah (2.21.3)
|
loofah (2.21.4)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.12.0)
|
nokogiri (>= 1.12.0)
|
||||||
mail (2.8.1)
|
mail (2.8.1)
|
||||||
|
@ -442,7 +432,7 @@ GEM
|
||||||
net-imap
|
net-imap
|
||||||
net-pop
|
net-pop
|
||||||
net-smtp
|
net-smtp
|
||||||
marcel (1.0.2)
|
marcel (1.0.4)
|
||||||
mario-redis-lock (1.2.1)
|
mario-redis-lock (1.2.1)
|
||||||
redis (>= 3.0.5)
|
redis (>= 3.0.5)
|
||||||
matrix (0.4.2)
|
matrix (0.4.2)
|
||||||
|
@ -456,7 +446,7 @@ GEM
|
||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
mime-types-data (3.2023.0808)
|
mime-types-data (3.2023.0808)
|
||||||
mini_mime (1.1.5)
|
mini_mime (1.1.5)
|
||||||
mini_portile2 (2.8.4)
|
mini_portile2 (2.8.7)
|
||||||
minitest (5.19.0)
|
minitest (5.19.0)
|
||||||
msgpack (1.7.1)
|
msgpack (1.7.1)
|
||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
|
@ -471,28 +461,33 @@ GEM
|
||||||
net-ldap (0.18.0)
|
net-ldap (0.18.0)
|
||||||
net-pop (0.1.2)
|
net-pop (0.1.2)
|
||||||
net-protocol
|
net-protocol
|
||||||
net-protocol (0.2.1)
|
net-protocol (0.2.2)
|
||||||
timeout
|
timeout
|
||||||
net-scp (4.0.0)
|
net-scp (4.0.0)
|
||||||
net-ssh (>= 2.6.5, < 8.0.0)
|
net-ssh (>= 2.6.5, < 8.0.0)
|
||||||
net-smtp (0.3.3)
|
net-smtp (0.3.4)
|
||||||
net-protocol
|
net-protocol
|
||||||
net-ssh (7.1.0)
|
net-ssh (7.1.0)
|
||||||
nio4r (2.5.9)
|
nio4r (2.7.3)
|
||||||
nokogiri (1.15.4)
|
nokogiri (1.16.7)
|
||||||
mini_portile2 (~> 2.8.2)
|
mini_portile2 (~> 2.8.2)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
|
nsa (0.3.0)
|
||||||
|
activesupport (>= 4.2, < 7.2)
|
||||||
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
|
sidekiq (>= 3.5)
|
||||||
|
statsd-ruby (~> 1.4, >= 1.4.0)
|
||||||
oj (3.16.1)
|
oj (3.16.1)
|
||||||
omniauth (2.1.1)
|
omniauth (2.1.2)
|
||||||
hashie (>= 3.4.6)
|
hashie (>= 3.4.6)
|
||||||
rack (>= 2.2.3)
|
rack (>= 2.2.3)
|
||||||
rack-protection
|
rack-protection
|
||||||
omniauth-rails_csrf_protection (1.0.1)
|
omniauth-rails_csrf_protection (1.0.1)
|
||||||
actionpack (>= 4.2)
|
actionpack (>= 4.2)
|
||||||
omniauth (~> 2.0)
|
omniauth (~> 2.0)
|
||||||
omniauth-saml (2.1.0)
|
omniauth-saml (2.1.2)
|
||||||
omniauth (~> 2.0)
|
omniauth (~> 2.1)
|
||||||
ruby-saml (~> 1.12)
|
ruby-saml (~> 1.17)
|
||||||
omniauth_openid_connect (0.6.1)
|
omniauth_openid_connect (0.6.1)
|
||||||
omniauth (>= 1.9, < 3)
|
omniauth (>= 1.9, < 3)
|
||||||
openid_connect (~> 1.1)
|
openid_connect (~> 1.1)
|
||||||
|
@ -519,10 +514,9 @@ GEM
|
||||||
parslet (2.0.0)
|
parslet (2.0.0)
|
||||||
pastel (0.8.0)
|
pastel (0.8.0)
|
||||||
tty-color (~> 0.5)
|
tty-color (~> 0.5)
|
||||||
pg (1.5.4)
|
pg (1.5.5)
|
||||||
pghero (3.3.4)
|
pghero (3.3.4)
|
||||||
activerecord (>= 6)
|
activerecord (>= 6)
|
||||||
posix-spawn (0.3.15)
|
|
||||||
premailer (1.21.0)
|
premailer (1.21.0)
|
||||||
addressable
|
addressable
|
||||||
css_parser (>= 1.12.0)
|
css_parser (>= 1.12.0)
|
||||||
|
@ -533,16 +527,16 @@ GEM
|
||||||
premailer (~> 1.7, >= 1.7.9)
|
premailer (~> 1.7, >= 1.7.9)
|
||||||
private_address_check (0.5.0)
|
private_address_check (0.5.0)
|
||||||
public_suffix (5.0.3)
|
public_suffix (5.0.3)
|
||||||
puma (6.3.1)
|
puma (6.4.3)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
pundit (2.3.0)
|
pundit (2.3.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
raabro (1.4.0)
|
raabro (1.4.0)
|
||||||
racc (1.7.1)
|
racc (1.8.1)
|
||||||
rack (2.2.8)
|
rack (2.2.9)
|
||||||
rack-attack (6.7.0)
|
rack-attack (6.7.0)
|
||||||
rack (>= 1.0, < 4)
|
rack (>= 1.0, < 4)
|
||||||
rack-cors (2.0.1)
|
rack-cors (2.0.2)
|
||||||
rack (>= 2.0.0)
|
rack (>= 2.0.0)
|
||||||
rack-oauth2 (1.21.3)
|
rack-oauth2 (1.21.3)
|
||||||
activesupport
|
activesupport
|
||||||
|
@ -550,26 +544,26 @@ GEM
|
||||||
httpclient
|
httpclient
|
||||||
json-jwt (>= 1.11.0)
|
json-jwt (>= 1.11.0)
|
||||||
rack (>= 2.1.0)
|
rack (>= 2.1.0)
|
||||||
rack-protection (3.0.5)
|
rack-protection (3.0.6)
|
||||||
rack
|
rack
|
||||||
rack-proxy (0.7.6)
|
rack-proxy (0.7.6)
|
||||||
rack
|
rack
|
||||||
rack-test (2.1.0)
|
rack-test (2.1.0)
|
||||||
rack (>= 1.3)
|
rack (>= 1.3)
|
||||||
rails (7.0.8)
|
rails (7.0.8.4)
|
||||||
actioncable (= 7.0.8)
|
actioncable (= 7.0.8.4)
|
||||||
actionmailbox (= 7.0.8)
|
actionmailbox (= 7.0.8.4)
|
||||||
actionmailer (= 7.0.8)
|
actionmailer (= 7.0.8.4)
|
||||||
actionpack (= 7.0.8)
|
actionpack (= 7.0.8.4)
|
||||||
actiontext (= 7.0.8)
|
actiontext (= 7.0.8.4)
|
||||||
actionview (= 7.0.8)
|
actionview (= 7.0.8.4)
|
||||||
activejob (= 7.0.8)
|
activejob (= 7.0.8.4)
|
||||||
activemodel (= 7.0.8)
|
activemodel (= 7.0.8.4)
|
||||||
activerecord (= 7.0.8)
|
activerecord (= 7.0.8.4)
|
||||||
activestorage (= 7.0.8)
|
activestorage (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8)
|
activesupport (= 7.0.8.4)
|
||||||
bundler (>= 1.15.0)
|
bundler (>= 1.15.0)
|
||||||
railties (= 7.0.8)
|
railties (= 7.0.8.4)
|
||||||
rails-controller-testing (1.0.5)
|
rails-controller-testing (1.0.5)
|
||||||
actionpack (>= 5.0.1.rc1)
|
actionpack (>= 5.0.1.rc1)
|
||||||
actionview (>= 5.0.1.rc1)
|
actionview (>= 5.0.1.rc1)
|
||||||
|
@ -584,16 +578,17 @@ GEM
|
||||||
rails-i18n (7.0.7)
|
rails-i18n (7.0.7)
|
||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
railties (>= 6.0.0, < 8)
|
railties (>= 6.0.0, < 8)
|
||||||
railties (7.0.8)
|
railties (7.0.8.4)
|
||||||
actionpack (= 7.0.8)
|
actionpack (= 7.0.8.4)
|
||||||
activesupport (= 7.0.8)
|
activesupport (= 7.0.8.4)
|
||||||
method_source
|
method_source
|
||||||
rake (>= 12.2)
|
rake (>= 12.2)
|
||||||
thor (~> 1.0)
|
thor (~> 1.0)
|
||||||
zeitwerk (~> 2.5)
|
zeitwerk (~> 2.5)
|
||||||
rainbow (3.1.1)
|
rainbow (3.1.1)
|
||||||
rake (13.0.6)
|
rake (13.0.6)
|
||||||
rdf (3.2.11)
|
rdf (3.3.1)
|
||||||
|
bcp47_spec (~> 0.2)
|
||||||
link_header (~> 0.0, >= 0.0.8)
|
link_header (~> 0.0, >= 0.0.8)
|
||||||
rdf-normalize (0.6.1)
|
rdf-normalize (0.6.1)
|
||||||
rdf (~> 3.2)
|
rdf (~> 3.2)
|
||||||
|
@ -609,8 +604,8 @@ GEM
|
||||||
responders (3.1.0)
|
responders (3.1.0)
|
||||||
actionpack (>= 5.2)
|
actionpack (>= 5.2)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
rexml (3.2.6)
|
rexml (3.3.7)
|
||||||
rotp (6.2.2)
|
rotp (6.3.0)
|
||||||
rouge (4.1.2)
|
rouge (4.1.2)
|
||||||
rpam2 (4.0.2)
|
rpam2 (4.0.2)
|
||||||
rqrcode (2.2.0)
|
rqrcode (2.2.0)
|
||||||
|
@ -673,7 +668,7 @@ GEM
|
||||||
rubocop-factory_bot (~> 2.22)
|
rubocop-factory_bot (~> 2.22)
|
||||||
ruby-prof (1.6.3)
|
ruby-prof (1.6.3)
|
||||||
ruby-progressbar (1.13.0)
|
ruby-progressbar (1.13.0)
|
||||||
ruby-saml (1.15.0)
|
ruby-saml (1.17.0)
|
||||||
nokogiri (>= 1.13.10)
|
nokogiri (>= 1.13.10)
|
||||||
rexml
|
rexml
|
||||||
ruby2_keywords (0.0.5)
|
ruby2_keywords (0.0.5)
|
||||||
|
@ -693,7 +688,7 @@ GEM
|
||||||
rubyzip (>= 1.2.2, < 3.0)
|
rubyzip (>= 1.2.2, < 3.0)
|
||||||
websocket (~> 1.0)
|
websocket (~> 1.0)
|
||||||
semantic_range (3.0.0)
|
semantic_range (3.0.0)
|
||||||
sidekiq (6.5.10)
|
sidekiq (6.5.12)
|
||||||
connection_pool (>= 2.2.5, < 3)
|
connection_pool (>= 2.2.5, < 3)
|
||||||
rack (~> 2.0)
|
rack (~> 2.0)
|
||||||
redis (>= 4.5.0, < 5)
|
redis (>= 4.5.0, < 5)
|
||||||
|
@ -703,7 +698,7 @@ GEM
|
||||||
rufus-scheduler (~> 3.2)
|
rufus-scheduler (~> 3.2)
|
||||||
sidekiq (>= 6, < 8)
|
sidekiq (>= 6, < 8)
|
||||||
tilt (>= 1.4.0)
|
tilt (>= 1.4.0)
|
||||||
sidekiq-unique-jobs (7.1.29)
|
sidekiq-unique-jobs (7.1.33)
|
||||||
brpoplpush-redis_script (> 0.1.1, <= 2.0.0)
|
brpoplpush-redis_script (> 0.1.1, <= 2.0.0)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.5)
|
concurrent-ruby (~> 1.0, >= 1.0.5)
|
||||||
redis (< 5.0)
|
redis (< 5.0)
|
||||||
|
@ -748,9 +743,9 @@ GEM
|
||||||
terrapin (0.6.0)
|
terrapin (0.6.0)
|
||||||
climate_control (>= 0.0.3, < 1.0)
|
climate_control (>= 0.0.3, < 1.0)
|
||||||
test-prof (1.2.3)
|
test-prof (1.2.3)
|
||||||
thor (1.2.2)
|
thor (1.3.1)
|
||||||
tilt (2.2.0)
|
tilt (2.2.0)
|
||||||
timeout (0.4.0)
|
timeout (0.4.1)
|
||||||
tpm-key_attestation (0.12.0)
|
tpm-key_attestation (0.12.0)
|
||||||
bindata (~> 2.4)
|
bindata (~> 2.4)
|
||||||
openssl (> 2.0)
|
openssl (> 2.0)
|
||||||
|
@ -814,7 +809,7 @@ GEM
|
||||||
xorcist (1.1.3)
|
xorcist (1.1.3)
|
||||||
xpath (3.2.0)
|
xpath (3.2.0)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.8)
|
||||||
zeitwerk (2.6.11)
|
zeitwerk (2.6.16)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
ruby
|
ruby
|
||||||
|
@ -877,6 +872,7 @@ DEPENDENCIES
|
||||||
letter_opener_web (~> 2.0)
|
letter_opener_web (~> 2.0)
|
||||||
link_header (~> 0.0)
|
link_header (~> 0.0)
|
||||||
lograge (~> 0.12)
|
lograge (~> 0.12)
|
||||||
|
mail (~> 2.8)
|
||||||
mario-redis-lock (~> 1.2)
|
mario-redis-lock (~> 1.2)
|
||||||
md-paperclip-azure (~> 2.2)
|
md-paperclip-azure (~> 2.2)
|
||||||
memory_profiler
|
memory_profiler
|
||||||
|
@ -884,7 +880,7 @@ DEPENDENCIES
|
||||||
net-http (~> 0.3.2)
|
net-http (~> 0.3.2)
|
||||||
net-ldap (~> 0.18)
|
net-ldap (~> 0.18)
|
||||||
nokogiri (~> 1.15)
|
nokogiri (~> 1.15)
|
||||||
nsa!
|
nsa
|
||||||
oj (~> 3.14)
|
oj (~> 3.14)
|
||||||
omniauth (~> 2.0)
|
omniauth (~> 2.0)
|
||||||
omniauth-cas!
|
omniauth-cas!
|
||||||
|
@ -895,7 +891,6 @@ DEPENDENCIES
|
||||||
parslet
|
parslet
|
||||||
pg (~> 1.5)
|
pg (~> 1.5)
|
||||||
pghero
|
pghero
|
||||||
posix-spawn
|
|
||||||
premailer-rails
|
premailer-rails
|
||||||
private_address_check (~> 0.5)
|
private_address_check (~> 0.5)
|
||||||
public_suffix (~> 5.0)
|
public_suffix (~> 5.0)
|
||||||
|
|
12
SECURITY.md
12
SECURITY.md
|
@ -13,10 +13,8 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through
|
||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
| ------- | ---------------- |
|
| ------- | --------- |
|
||||||
| 4.2.x | Yes |
|
| 4.2.x | Yes |
|
||||||
| 4.1.x | Yes |
|
| 4.1.x | Yes |
|
||||||
| 4.0.x | Until 2023-10-31 |
|
| < 4.1 | No |
|
||||||
| 3.5.x | Until 2023-12-31 |
|
|
||||||
| < 3.5 | No |
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class AccountsIndex < Chewy::Index
|
class AccountsIndex < Chewy::Index
|
||||||
|
include DatetimeClampingConcern
|
||||||
|
|
||||||
DEVELOPMENT_SETTINGS = {
|
DEVELOPMENT_SETTINGS = {
|
||||||
filter: {
|
filter: {
|
||||||
english_stop: {
|
english_stop: {
|
||||||
|
@ -153,7 +155,7 @@ class AccountsIndex < Chewy::Index
|
||||||
field(:following_count, type: 'long', value: ->(account) { account.public_following_count })
|
field(:following_count, type: 'long', value: ->(account) { account.public_following_count })
|
||||||
field(:followers_count, type: 'long', value: ->(account) { account.public_followers_count })
|
field(:followers_count, type: 'long', value: ->(account) { account.public_followers_count })
|
||||||
field(:properties, type: 'keyword', value: ->(account) { account.searchable_properties })
|
field(:properties, type: 'keyword', value: ->(account) { account.searchable_properties })
|
||||||
field(:last_status_at, type: 'date', value: ->(account) { account.last_status_at || account.created_at })
|
field(:last_status_at, type: 'date', value: ->(account) { clamp_date(account.last_status_at || account.created_at) })
|
||||||
field(:domain, type: 'keyword', value: ->(account) { account.domain || '' })
|
field(:domain, type: 'keyword', value: ->(account) { account.domain || '' })
|
||||||
field(:display_name, type: 'text', analyzer: 'verbatim') { field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'verbatim' }
|
field(:display_name, type: 'text', analyzer: 'verbatim') { field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'verbatim' }
|
||||||
field(:username, type: 'text', analyzer: 'verbatim', value: ->(account) { [account.username, account.domain].compact.join('@') }) { field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'verbatim' }
|
field(:username, type: 'text', analyzer: 'verbatim', value: ->(account) { [account.username, account.domain].compact.join('@') }) { field :edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'verbatim' }
|
||||||
|
|
14
app/chewy/concerns/datetime_clamping_concern.rb
Normal file
14
app/chewy/concerns/datetime_clamping_concern.rb
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module DatetimeClampingConcern
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
MIN_ISO8601_DATETIME = '0000-01-01T00:00:00Z'.to_datetime.freeze
|
||||||
|
MAX_ISO8601_DATETIME = '9999-12-31T23:59:59Z'.to_datetime.freeze
|
||||||
|
|
||||||
|
class_methods do
|
||||||
|
def clamp_date(datetime)
|
||||||
|
datetime.clamp(MIN_ISO8601_DATETIME, MAX_ISO8601_DATETIME)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class PublicStatusesIndex < Chewy::Index
|
class PublicStatusesIndex < Chewy::Index
|
||||||
|
include DatetimeClampingConcern
|
||||||
|
|
||||||
DEVELOPMENT_SETTINGS = {
|
DEVELOPMENT_SETTINGS = {
|
||||||
filter: {
|
filter: {
|
||||||
english_stop: {
|
english_stop: {
|
||||||
|
@ -154,6 +156,6 @@ class PublicStatusesIndex < Chewy::Index
|
||||||
field(:language, type: 'keyword')
|
field(:language, type: 'keyword')
|
||||||
field(:domain, type: 'keyword', value: ->(status) { status.account.domain || '' })
|
field(:domain, type: 'keyword', value: ->(status) { status.account.domain || '' })
|
||||||
field(:properties, type: 'keyword', value: ->(status) { status.searchable_properties })
|
field(:properties, type: 'keyword', value: ->(status) { status.searchable_properties })
|
||||||
field(:created_at, type: 'date')
|
field(:created_at, type: 'date', value: ->(status) { clamp_date(status.created_at) })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class StatusesIndex < Chewy::Index
|
class StatusesIndex < Chewy::Index
|
||||||
|
include DatetimeClampingConcern
|
||||||
|
|
||||||
DEVELOPMENT_SETTINGS = {
|
DEVELOPMENT_SETTINGS = {
|
||||||
filter: {
|
filter: {
|
||||||
english_stop: {
|
english_stop: {
|
||||||
|
@ -184,6 +186,6 @@ class StatusesIndex < Chewy::Index
|
||||||
field(:language, type: 'keyword')
|
field(:language, type: 'keyword')
|
||||||
field(:domain, type: 'keyword', value: ->(status) { status.account.domain || '' })
|
field(:domain, type: 'keyword', value: ->(status) { status.account.domain || '' })
|
||||||
field(:properties, type: 'keyword', value: ->(status) { status.searchable_properties })
|
field(:properties, type: 'keyword', value: ->(status) { status.searchable_properties })
|
||||||
field(:created_at, type: 'date')
|
field(:created_at, type: 'date', value: ->(status) { clamp_date(status.created_at) })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class TagsIndex < Chewy::Index
|
class TagsIndex < Chewy::Index
|
||||||
|
include DatetimeClampingConcern
|
||||||
|
|
||||||
settings index: index_preset(refresh_interval: '30s'), analysis: {
|
settings index: index_preset(refresh_interval: '30s'), analysis: {
|
||||||
analyzer: {
|
analyzer: {
|
||||||
content: {
|
content: {
|
||||||
|
@ -42,6 +44,6 @@ class TagsIndex < Chewy::Index
|
||||||
field(:name, type: 'text', analyzer: 'content', value: :display_name) { field(:edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content') }
|
field(:name, type: 'text', analyzer: 'content', value: :display_name) { field(:edge_ngram, type: 'text', analyzer: 'edge_ngram', search_analyzer: 'content') }
|
||||||
field(:reviewed, type: 'boolean', value: ->(tag) { tag.reviewed? })
|
field(:reviewed, type: 'boolean', value: ->(tag) { tag.reviewed? })
|
||||||
field(:usage, type: 'long', value: ->(tag, crutches) { tag.history.aggregate(crutches.time_period).accounts })
|
field(:usage, type: 'long', value: ->(tag, crutches) { tag.history.aggregate(crutches.time_period).accounts })
|
||||||
field(:last_status_at, type: 'date', value: ->(tag) { tag.last_status_at || tag.created_at })
|
field(:last_status_at, type: 'date', value: ->(tag) { clamp_date(tag.last_status_at || tag.created_at) })
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,8 +5,6 @@ class ActivityPub::ReferencesController < ActivityPub::BaseController
|
||||||
include Authorization
|
include Authorization
|
||||||
include AccountOwnedConcern
|
include AccountOwnedConcern
|
||||||
|
|
||||||
REFERENCES_LIMIT = 5
|
|
||||||
|
|
||||||
before_action :require_signature!, if: :authorized_fetch_mode?
|
before_action :require_signature!, if: :authorized_fetch_mode?
|
||||||
before_action :set_status
|
before_action :set_status
|
||||||
|
|
||||||
|
@ -40,17 +38,21 @@ class ActivityPub::ReferencesController < ActivityPub::BaseController
|
||||||
@results ||= begin
|
@results ||= begin
|
||||||
references = @status.reference_objects.order(target_status_id: :asc)
|
references = @status.reference_objects.order(target_status_id: :asc)
|
||||||
references = references.where('target_status_id > ?', page_params[:min_id]) if page_params[:min_id].present?
|
references = references.where('target_status_id > ?', page_params[:min_id]) if page_params[:min_id].present?
|
||||||
references = references.limit(limit_param(REFERENCES_LIMIT))
|
references = references.limit(limit_param(references_limit))
|
||||||
references.pluck(:target_status_id)
|
references.pluck(:target_status_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def references_limit
|
||||||
|
StatusReference::REFERENCES_LIMIT
|
||||||
|
end
|
||||||
|
|
||||||
def pagination_min_id
|
def pagination_min_id
|
||||||
results.last
|
results.last
|
||||||
end
|
end
|
||||||
|
|
||||||
def records_continue?
|
def records_continue?
|
||||||
results.size == limit_param(REFERENCES_LIMIT)
|
results.size == limit_param(references_limit)
|
||||||
end
|
end
|
||||||
|
|
||||||
def references_collection_presenter
|
def references_collection_presenter
|
||||||
|
|
|
@ -14,7 +14,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
|
||||||
before_action :set_replies
|
before_action :set_replies
|
||||||
|
|
||||||
def index
|
def index
|
||||||
expires_in 0, public: public_fetch_mode?
|
expires_in 0, public: @status.distributable? && public_fetch_mode?
|
||||||
render json: replies_collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', skip_activities: true
|
render json: replies_collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', skip_activities: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ module Admin
|
||||||
account_action.save!
|
account_action.save!
|
||||||
|
|
||||||
if account_action.with_report?
|
if account_action.with_report?
|
||||||
redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: params[:report_id])
|
redirect_to admin_reports_path, notice: I18n.t('admin.reports.processed_msg', id: resource_params[:report_id])
|
||||||
else
|
else
|
||||||
redirect_to admin_account_path(@account.id)
|
redirect_to admin_account_path(@account.id)
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,6 +25,8 @@ class Admin::DomainAllowsController < Admin::BaseController
|
||||||
def destroy
|
def destroy
|
||||||
authorize @domain_allow, :destroy?
|
authorize @domain_allow, :destroy?
|
||||||
UnallowDomainService.new.call(@domain_allow)
|
UnallowDomainService.new.call(@domain_allow)
|
||||||
|
log_action :destroy, @domain_allow
|
||||||
|
|
||||||
redirect_to admin_instances_path, notice: I18n.t('admin.domain_allows.destroyed_msg')
|
redirect_to admin_instances_path, notice: I18n.t('admin.domain_allows.destroyed_msg')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -88,18 +88,18 @@ module Admin
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_params
|
def update_params
|
||||||
params.require(:domain_block).permit(:severity, :reject_media, :reject_favourite, :reject_reply, :reject_reply_exclude_followers, :reject_send_not_public_searchability, :reject_send_public_unlisted, :reject_send_dissubscribable, :reject_send_media, :reject_send_sensitive, :reject_hashtag,
|
params.require(:domain_block).permit(:severity, :reject_media, :reject_favourite, :reject_reply, :reject_reply_exclude_followers, :reject_send_sensitive, :reject_hashtag,
|
||||||
:reject_straight_follow, :reject_new_follow, :detect_invalid_subscription, :reject_reports, :private_comment, :public_comment, :obfuscate, :hidden, :hidden_anonymous)
|
:reject_straight_follow, :reject_new_follow, :detect_invalid_subscription, :reject_reports, :private_comment, :public_comment, :obfuscate, :hidden)
|
||||||
end
|
end
|
||||||
|
|
||||||
def resource_params
|
def resource_params
|
||||||
params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_favourite, :reject_reply, :reject_reply_exclude_followers, :reject_send_not_public_searchability, :reject_send_public_unlisted, :reject_send_dissubscribable, :reject_send_media, :reject_send_sensitive, :reject_hashtag,
|
params.require(:domain_block).permit(:domain, :severity, :reject_media, :reject_favourite, :reject_reply, :reject_reply_exclude_followers, :reject_send_sensitive, :reject_hashtag,
|
||||||
:reject_straight_follow, :reject_new_follow, :detect_invalid_subscription, :reject_reports, :private_comment, :public_comment, :obfuscate, :hidden, :hidden_anonymous)
|
:reject_straight_follow, :reject_new_follow, :detect_invalid_subscription, :reject_reports, :private_comment, :public_comment, :obfuscate, :hidden)
|
||||||
end
|
end
|
||||||
|
|
||||||
def form_domain_block_batch_params
|
def form_domain_block_batch_params
|
||||||
params.require(:form_domain_block_batch).permit(domain_blocks_attributes: [:enabled, :domain, :severity, :reject_media, :reject_favourite, :reject_reply, :reject_reply_exclude_followers, :reject_send_not_public_searchability, :reject_send_public_unlisted, :reject_send_dissubscribable, :reject_send_media,
|
params.require(:form_domain_block_batch).permit(domain_blocks_attributes: [:enabled, :domain, :severity, :reject_media, :reject_favourite, :reject_reply, :reject_reply_exclude_followers,
|
||||||
:reject_send_sensitive, :reject_hashtag, :reject_straight_follow, :reject_new_follow, :detect_invalid_subscription, :reject_reports, :private_comment, :public_comment, :obfuscate, :hidden, :hidden_anonymous])
|
:reject_send_sensitive, :reject_hashtag, :reject_straight_follow, :reject_new_follow, :detect_invalid_subscription, :reject_reports, :private_comment, :public_comment, :obfuscate, :hidden])
|
||||||
end
|
end
|
||||||
|
|
||||||
def action_from_button
|
def action_from_button
|
||||||
|
|
|
@ -25,6 +25,6 @@ class Api::V1::Accounts::NotesController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def relationships_presenter
|
def relationships_presenter
|
||||||
AccountRelationshipsPresenter.new([@account.id], current_user.account_id)
|
AccountRelationshipsPresenter.new([@account], current_user.account_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,6 +25,6 @@ class Api::V1::Accounts::PinsController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def relationships_presenter
|
def relationships_presenter
|
||||||
AccountRelationshipsPresenter.new([@account.id], current_user.account_id)
|
AccountRelationshipsPresenter.new([@account], current_user.account_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,11 +5,10 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
|
|
||||||
def index
|
def index
|
||||||
accounts = Account.without_suspended.where(id: account_ids).select('id')
|
@accounts = Account.without_suspended.where(id: account_ids).select(:id, :domain).to_a
|
||||||
# .where doesn't guarantee that our results are in the same order
|
# .where doesn't guarantee that our results are in the same order
|
||||||
# we requested them, so return the "right" order to the requestor.
|
# we requested them, so return the "right" order to the requestor.
|
||||||
@accounts = accounts.index_by(&:id).values_at(*account_ids).compact
|
render json: @accounts.index_by(&:id).values_at(*account_ids).compact, each_serializer: REST::RelationshipSerializer, relationships: relationships
|
||||||
render json: @accounts, each_serializer: REST::RelationshipSerializer, relationships: relationships
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -86,7 +86,7 @@ class Api::V1::AccountsController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def relationships(**options)
|
def relationships(**options)
|
||||||
AccountRelationshipsPresenter.new([@account.id], current_user.account_id, **options)
|
AccountRelationshipsPresenter.new([@account], current_user.account_id, **options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def account_params
|
def account_params
|
||||||
|
|
|
@ -29,10 +29,11 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
|
||||||
def create
|
def create
|
||||||
authorize :domain_block, :create?
|
authorize :domain_block, :create?
|
||||||
|
|
||||||
|
@domain_block = DomainBlock.new(resource_params)
|
||||||
existing_domain_block = resource_params[:domain].present? ? DomainBlock.rule_for(resource_params[:domain]) : nil
|
existing_domain_block = resource_params[:domain].present? ? DomainBlock.rule_for(resource_params[:domain]) : nil
|
||||||
return render json: existing_domain_block, serializer: REST::Admin::ExistingDomainBlockErrorSerializer, status: 422 if existing_domain_block.present?
|
return render json: existing_domain_block, serializer: REST::Admin::ExistingDomainBlockErrorSerializer, status: 422 if conflicts_with_existing_block?(@domain_block, existing_domain_block)
|
||||||
|
|
||||||
@domain_block = DomainBlock.create!(resource_params)
|
@domain_block.save!
|
||||||
DomainBlockWorker.perform_async(@domain_block.id)
|
DomainBlockWorker.perform_async(@domain_block.id)
|
||||||
log_action :create, @domain_block
|
log_action :create, @domain_block
|
||||||
render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer
|
render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer
|
||||||
|
@ -55,6 +56,10 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def conflicts_with_existing_block?(domain_block, existing_domain_block)
|
||||||
|
existing_domain_block.present? && (existing_domain_block.domain == TagManager.instance.normalize_domain(domain_block.domain) || !domain_block.stricter_than?(existing_domain_block))
|
||||||
|
end
|
||||||
|
|
||||||
def set_domain_blocks
|
def set_domain_blocks
|
||||||
@domain_blocks = filtered_domain_blocks.order(id: :desc).to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
|
@domain_blocks = filtered_domain_blocks.order(id: :desc).to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
|
||||||
end
|
end
|
||||||
|
@ -69,8 +74,8 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def domain_block_params
|
def domain_block_params
|
||||||
params.permit(:severity, :reject_media, :reject_favourite, :reject_reply, :reject_reply_exclude_followers, :reject_reports, :reject_send_not_public_searchability, :reject_send_public_unlisted, :reject_send_dissubscribable, :reject_send_media, :reject_send_sensitive, :reject_hashtag, :reject_straight_follow,
|
params.permit(:severity, :reject_media, :reject_favourite, :reject_reply, :reject_reply_exclude_followers, :reject_reports, :reject_send_sensitive, :reject_hashtag, :reject_straight_follow,
|
||||||
:reject_new_follow, :detect_invalid_subscription, :private_comment, :public_comment, :obfuscate, :hidden, :hidden_anonymous)
|
:reject_new_follow, :detect_invalid_subscription, :private_comment, :public_comment, :obfuscate, :hidden)
|
||||||
end
|
end
|
||||||
|
|
||||||
def insert_pagination_headers
|
def insert_pagination_headers
|
||||||
|
@ -102,7 +107,7 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def resource_params
|
def resource_params
|
||||||
params.permit(:domain, :severity, :reject_media, :reject_favourite, :reject_reply, :reject_reply_exclude_followers, :reject_send_not_public_searchability, :reject_send_public_unlisted, :reject_send_dissubscribable, :reject_send_media, :reject_send_sensitive, :reject_hashtag, :reject_straight_follow,
|
params.permit(:domain, :severity, :reject_media, :reject_favourite, :reject_reply, :reject_reply_exclude_followers, :reject_send_sensitive, :reject_hashtag, :reject_straight_follow,
|
||||||
:reject_new_follow, :detect_invalid_subscription, :reject_reports, :private_comment, :public_comment, :obfuscate, :hidden, :hidden_anonymous)
|
:reject_new_follow, :detect_invalid_subscription, :reject_reports, :private_comment, :public_comment, :obfuscate, :hidden)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -12,6 +12,10 @@ class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_recently_used_tags
|
def set_recently_used_tags
|
||||||
@recently_used_tags = Tag.recently_used(current_account).where.not(id: current_account.featured_tags).limit(10)
|
@recently_used_tags = Tag.recently_used(current_account).where.not(id: featured_tag_ids).limit(10)
|
||||||
|
end
|
||||||
|
|
||||||
|
def featured_tag_ids
|
||||||
|
current_account.featured_tags.pluck(:tag_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,11 +25,11 @@ class Api::V1::FollowRequestsController < Api::BaseController
|
||||||
private
|
private
|
||||||
|
|
||||||
def account
|
def account
|
||||||
Account.find(params[:id])
|
@account ||= Account.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def relationships(**options)
|
def relationships(**options)
|
||||||
AccountRelationshipsPresenter.new([params[:id]], current_user.account_id, **options)
|
AccountRelationshipsPresenter.new([account], current_user.account_id, **options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def load_accounts
|
def load_accounts
|
||||||
|
|
|
@ -26,6 +26,5 @@ class Api::V1::Instances::DomainBlocksController < Api::BaseController
|
||||||
|
|
||||||
def set_domain_blocks
|
def set_domain_blocks
|
||||||
@domain_blocks = DomainBlock.with_user_facing_limitations.by_severity
|
@domain_blocks = DomainBlock.with_user_facing_limitations.by_severity
|
||||||
@domain_blocks = @domain_blocks.filter { |block| !block.hidden_anonymous } unless user_signed_in?
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,7 @@ class Api::V1::ScheduledStatusesController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, except: [:update, :destroy]
|
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, except: [:update, :destroy]
|
||||||
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:update, :destroy]
|
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:update, :destroy]
|
||||||
|
|
||||||
|
before_action :require_user!
|
||||||
before_action :set_statuses, only: :index
|
before_action :set_statuses, only: :index
|
||||||
before_action :set_status, except: :index
|
before_action :set_status, except: :index
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ class Api::V1::Statuses::TranslationsController < Api::BaseController
|
||||||
include Authorization
|
include Authorization
|
||||||
|
|
||||||
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }
|
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }
|
||||||
|
before_action :require_user!
|
||||||
before_action :set_status
|
before_action :set_status
|
||||||
before_action :set_translation
|
before_action :set_translation
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
class Api::V1::StreamingController < Api::BaseController
|
class Api::V1::StreamingController < Api::BaseController
|
||||||
def index
|
def index
|
||||||
if Rails.configuration.x.streaming_api_base_url == request.host
|
if same_host?
|
||||||
not_found
|
not_found
|
||||||
else
|
else
|
||||||
redirect_to streaming_api_url, status: 301, allow_other_host: true
|
redirect_to streaming_api_url, status: 301, allow_other_host: true
|
||||||
|
@ -11,9 +11,16 @@ class Api::V1::StreamingController < Api::BaseController
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def same_host?
|
||||||
|
base_url = Addressable::URI.parse(Rails.configuration.x.streaming_api_base_url)
|
||||||
|
request.host == base_url.host && request.port == (base_url.port || 80)
|
||||||
|
end
|
||||||
|
|
||||||
def streaming_api_url
|
def streaming_api_url
|
||||||
Addressable::URI.parse(request.url).tap do |uri|
|
Addressable::URI.parse(request.url).tap do |uri|
|
||||||
uri.host = Addressable::URI.parse(Rails.configuration.x.streaming_api_base_url).host
|
base_url = Addressable::URI.parse(Rails.configuration.x.streaming_api_base_url)
|
||||||
|
uri.host = base_url.host
|
||||||
|
uri.port = base_url.port
|
||||||
end.to_s
|
end.to_s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Timelines::PublicController < Api::BaseController
|
class Api::V1::Timelines::PublicController < Api::BaseController
|
||||||
|
before_action -> { authorize_if_got_token! :read, :'read:statuses' }
|
||||||
before_action :require_user!, only: [:show], if: :require_auth?
|
before_action :require_user!, only: [:show], if: :require_auth?
|
||||||
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Timelines::TagController < Api::BaseController
|
class Api::V1::Timelines::TagController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :read, :'read:statuses' }, only: :show, if: :require_auth?
|
before_action -> { authorize_if_got_token! :read, :'read:statuses' }
|
||||||
|
before_action :require_user!, if: :require_auth?
|
||||||
before_action :load_tag
|
before_action :load_tag
|
||||||
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||||
def self.provides_callback_for(provider)
|
def self.provides_callback_for(provider)
|
||||||
define_method provider do
|
define_method provider do
|
||||||
@provider = provider
|
@provider = provider
|
||||||
@user = User.find_for_oauth(request.env['omniauth.auth'], current_user)
|
@user = User.find_for_omniauth(request.env['omniauth.auth'], current_user)
|
||||||
|
|
||||||
if @user.persisted?
|
if @user.persisted?
|
||||||
record_login_activity
|
record_login_activity
|
||||||
|
@ -16,6 +16,9 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||||
session["devise.#{provider}_data"] = request.env['omniauth.auth']
|
session["devise.#{provider}_data"] = request.env['omniauth.auth']
|
||||||
redirect_to new_user_registration_url
|
redirect_to new_user_registration_url
|
||||||
end
|
end
|
||||||
|
rescue ActiveRecord::RecordInvalid
|
||||||
|
flash[:alert] = I18n.t('devise.failure.omniauth_user_creation_failure') if is_navigational_format?
|
||||||
|
redirect_to new_user_session_url
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Auth::SessionsController < Devise::SessionsController
|
class Auth::SessionsController < Devise::SessionsController
|
||||||
|
include Redisable
|
||||||
|
|
||||||
|
MAX_2FA_ATTEMPTS_PER_HOUR = 10
|
||||||
|
|
||||||
layout 'auth'
|
layout 'auth'
|
||||||
|
|
||||||
skip_before_action :require_no_authentication, only: [:create]
|
skip_before_action :require_no_authentication, only: [:create]
|
||||||
|
@ -134,9 +138,23 @@ class Auth::SessionsController < Devise::SessionsController
|
||||||
session.delete(:attempt_user_updated_at)
|
session.delete(:attempt_user_updated_at)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def clear_2fa_attempt_from_user(user)
|
||||||
|
redis.del(second_factor_attempts_key(user))
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_second_factor_rate_limits(user)
|
||||||
|
attempts, = redis.multi do |multi|
|
||||||
|
multi.incr(second_factor_attempts_key(user))
|
||||||
|
multi.expire(second_factor_attempts_key(user), 1.hour)
|
||||||
|
end
|
||||||
|
|
||||||
|
attempts >= MAX_2FA_ATTEMPTS_PER_HOUR
|
||||||
|
end
|
||||||
|
|
||||||
def on_authentication_success(user, security_measure)
|
def on_authentication_success(user, security_measure)
|
||||||
@on_authentication_success_called = true
|
@on_authentication_success_called = true
|
||||||
|
|
||||||
|
clear_2fa_attempt_from_user(user)
|
||||||
clear_attempt_from_session
|
clear_attempt_from_session
|
||||||
|
|
||||||
user.update_sign_in!(new_sign_in: true)
|
user.update_sign_in!(new_sign_in: true)
|
||||||
|
@ -168,4 +186,8 @@ class Auth::SessionsController < Devise::SessionsController
|
||||||
user_agent: request.user_agent
|
user_agent: request.user_agent
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def second_factor_attempts_key(user)
|
||||||
|
"2fa_auth_attempts:#{user.id}:#{Time.now.utc.hour}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -180,6 +180,16 @@ module CacheConcern
|
||||||
def render_with_cache(**options)
|
def render_with_cache(**options)
|
||||||
raise ArgumentError, 'Only JSON render calls are supported' unless options.key?(:json) || block_given?
|
raise ArgumentError, 'Only JSON render calls are supported' unless options.key?(:json) || block_given?
|
||||||
|
|
||||||
|
if options.delete(:cancel_cache)
|
||||||
|
if block_given?
|
||||||
|
options[:json] = yield
|
||||||
|
elsif options[:json].is_a?(Symbol)
|
||||||
|
options[:json] = send(options[:json])
|
||||||
|
end
|
||||||
|
|
||||||
|
return render(options)
|
||||||
|
end
|
||||||
|
|
||||||
key = options.delete(:key) || [[params[:controller], params[:action]].join('/'), options[:json].respond_to?(:cache_key) ? options[:json].cache_key : nil, options[:fields].nil? ? nil : options[:fields].join(',')].compact.join(':')
|
key = options.delete(:key) || [[params[:controller], params[:action]].join('/'), options[:json].respond_to?(:cache_key) ? options[:json].cache_key : nil, options[:fields].nil? ? nil : options[:fields].join(',')].compact.join(':')
|
||||||
expires_in = options.delete(:expires_in) || 3.minutes
|
expires_in = options.delete(:expires_in) || 3.minutes
|
||||||
body = Rails.cache.read(key, raw: true)
|
body = Rails.cache.read(key, raw: true)
|
||||||
|
@ -198,34 +208,19 @@ module CacheConcern
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO: Rename this method, as it does not perform any caching anymore.
|
||||||
def cache_collection(raw, klass)
|
def cache_collection(raw, klass)
|
||||||
return raw unless klass.respond_to?(:with_includes)
|
return raw unless klass.respond_to?(:preload_cacheable_associations)
|
||||||
|
|
||||||
raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
|
records = raw.to_a
|
||||||
return [] if raw.empty?
|
|
||||||
|
|
||||||
cached_keys_with_value = begin
|
klass.preload_cacheable_associations(records)
|
||||||
Rails.cache.read_multi(*raw).transform_keys(&:id).transform_values { |r| ActiveRecordCoder.load(r) }
|
|
||||||
rescue ActiveRecordCoder::Error
|
|
||||||
{} # The serialization format may have changed, let's pretend it's a cache miss.
|
|
||||||
end
|
|
||||||
|
|
||||||
uncached_ids = raw.map(&:id) - cached_keys_with_value.keys
|
records
|
||||||
|
|
||||||
klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!)
|
|
||||||
|
|
||||||
unless uncached_ids.empty?
|
|
||||||
uncached = klass.where(id: uncached_ids).with_includes.index_by(&:id)
|
|
||||||
|
|
||||||
uncached.each_value do |item|
|
|
||||||
Rails.cache.write(item, ActiveRecordCoder.dump(item))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
raw.filter_map { |item| cached_keys_with_value[item.id] || uncached[item.id] }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO: Rename this method, as it does not perform any caching anymore.
|
||||||
def cache_collection_paginated_by_id(raw, klass, limit, options)
|
def cache_collection_paginated_by_id(raw, klass, limit, options)
|
||||||
cache_collection raw.cache_ids.to_a_paginated_by_id(limit, options), klass
|
cache_collection raw.to_a_paginated_by_id(limit, options), klass
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -91,14 +91,23 @@ module SignatureVerification
|
||||||
raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if actor.nil?
|
raise SignatureVerificationError, "Public key not found for key #{signature_params['keyId']}" if actor.nil?
|
||||||
|
|
||||||
signature = Base64.decode64(signature_params['signature'])
|
signature = Base64.decode64(signature_params['signature'])
|
||||||
compare_signed_string = build_signed_string
|
compare_signed_string = build_signed_string(include_query_string: true)
|
||||||
|
|
||||||
return actor unless verify_signature(actor, signature, compare_signed_string).nil?
|
return actor unless verify_signature(actor, signature, compare_signed_string).nil?
|
||||||
|
|
||||||
|
# Compatibility quirk with older Mastodon versions
|
||||||
|
compare_signed_string = build_signed_string(include_query_string: false)
|
||||||
|
return actor unless verify_signature(actor, signature, compare_signed_string).nil?
|
||||||
|
|
||||||
actor = stoplight_wrap_request { actor_refresh_key!(actor) }
|
actor = stoplight_wrap_request { actor_refresh_key!(actor) }
|
||||||
|
|
||||||
raise SignatureVerificationError, "Could not refresh public key #{signature_params['keyId']}" if actor.nil?
|
raise SignatureVerificationError, "Could not refresh public key #{signature_params['keyId']}" if actor.nil?
|
||||||
|
|
||||||
|
compare_signed_string = build_signed_string(include_query_string: true)
|
||||||
|
return actor unless verify_signature(actor, signature, compare_signed_string).nil?
|
||||||
|
|
||||||
|
# Compatibility quirk with older Mastodon versions
|
||||||
|
compare_signed_string = build_signed_string(include_query_string: false)
|
||||||
return actor unless verify_signature(actor, signature, compare_signed_string).nil?
|
return actor unless verify_signature(actor, signature, compare_signed_string).nil?
|
||||||
|
|
||||||
fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)", signed_string: compare_signed_string, signature: signature_params['signature']
|
fail_with! "Verification failed for #{actor.to_log_human_identifier} #{actor.uri} using rsa-sha256 (RSASSA-PKCS1-v1_5 with SHA-256)", signed_string: compare_signed_string, signature: signature_params['signature']
|
||||||
|
@ -180,11 +189,18 @@ module SignatureVerification
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_signed_string
|
def build_signed_string(include_query_string: true)
|
||||||
signed_headers.map do |signed_header|
|
signed_headers.map do |signed_header|
|
||||||
case signed_header
|
case signed_header
|
||||||
when Request::REQUEST_TARGET
|
when Request::REQUEST_TARGET
|
||||||
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.path}"
|
if include_query_string
|
||||||
|
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.original_fullpath}"
|
||||||
|
else
|
||||||
|
# Current versions of Mastodon incorrectly omit the query string from the (request-target) pseudo-header.
|
||||||
|
# Therefore, temporarily support such incorrect signatures for compatibility.
|
||||||
|
# TODO: remove eventually some time after release of the fixed version
|
||||||
|
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.path}"
|
||||||
|
end
|
||||||
when '(created)'
|
when '(created)'
|
||||||
raise SignatureVerificationError, 'Invalid pseudo-header (created) for rsa-sha256' unless signature_algorithm == 'hs2019'
|
raise SignatureVerificationError, 'Invalid pseudo-header (created) for rsa-sha256' unless signature_algorithm == 'hs2019'
|
||||||
raise SignatureVerificationError, 'Pseudo-header (created) used but corresponding argument missing' if signature_params['created'].blank?
|
raise SignatureVerificationError, 'Pseudo-header (created) used but corresponding argument missing' if signature_params['created'].blank?
|
||||||
|
@ -250,7 +266,7 @@ module SignatureVerification
|
||||||
stoplight_wrap_request { ResolveAccountService.new.call(key_id.delete_prefix('acct:'), suppress_errors: false) }
|
stoplight_wrap_request { ResolveAccountService.new.call(key_id.delete_prefix('acct:'), suppress_errors: false) }
|
||||||
elsif !ActivityPub::TagManager.instance.local_uri?(key_id)
|
elsif !ActivityPub::TagManager.instance.local_uri?(key_id)
|
||||||
account = ActivityPub::TagManager.instance.uri_to_actor(key_id)
|
account = ActivityPub::TagManager.instance.uri_to_actor(key_id)
|
||||||
account ||= stoplight_wrap_request { ActivityPub::FetchRemoteKeyService.new.call(key_id, id: false, suppress_errors: false) }
|
account ||= stoplight_wrap_request { ActivityPub::FetchRemoteKeyService.new.call(key_id, suppress_errors: false) }
|
||||||
account
|
account
|
||||||
end
|
end
|
||||||
rescue Mastodon::PrivateNetworkAddressError => e
|
rescue Mastodon::PrivateNetworkAddressError => e
|
||||||
|
|
|
@ -65,6 +65,11 @@ module TwoFactorAuthenticationConcern
|
||||||
end
|
end
|
||||||
|
|
||||||
def authenticate_with_two_factor_via_otp(user)
|
def authenticate_with_two_factor_via_otp(user)
|
||||||
|
if check_second_factor_rate_limits(user)
|
||||||
|
flash.now[:alert] = I18n.t('users.rate_limited')
|
||||||
|
return prompt_for_two_factor(user)
|
||||||
|
end
|
||||||
|
|
||||||
if valid_otp_attempt?(user)
|
if valid_otp_attempt?(user)
|
||||||
on_authentication_success(user, :otp)
|
on_authentication_success(user, :otp)
|
||||||
else
|
else
|
||||||
|
|
|
@ -17,6 +17,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
Web::PushSubscription.unsubscribe_for(params[:id], current_resource_owner)
|
Web::PushSubscription.unsubscribe_for(params[:id], current_resource_owner)
|
||||||
|
Doorkeeper::Application.find_by(id: params[:id])&.close_streaming_sessions(current_resource_owner)
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ class RelationshipsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_relationships
|
def set_relationships
|
||||||
@relationships = AccountRelationshipsPresenter.new(@accounts.pluck(:id), current_user.account_id)
|
@relationships = AccountRelationshipsPresenter.new(@accounts, current_user.account_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def form_account_batch_params
|
def form_account_batch_params
|
||||||
|
|
|
@ -30,15 +30,15 @@ class StatusesController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
format.json do
|
format.json do
|
||||||
expires_in 3.minutes, public: true if @status.distributable? && public_fetch_mode?
|
expires_in 3.minutes, public: true if @status.distributable? && public_fetch_mode? && !misskey_software?
|
||||||
render_with_cache json: @status, content_type: 'application/activity+json', serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter
|
render_with_cache json: @status, content_type: 'application/activity+json', serializer: status_activity_serializer, adapter: ActivityPub::Adapter, cancel_cache: misskey_software?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def activity
|
def activity
|
||||||
expires_in 3.minutes, public: @status.distributable? && public_fetch_mode?
|
expires_in 3.minutes, public: @status.distributable? && public_fetch_mode? && !misskey_software?
|
||||||
render_with_cache json: ActivityPub::ActivityPresenter.from_status(@status), content_type: 'application/activity+json', serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter
|
render_with_cache json: ActivityPub::ActivityPresenter.from_status(@status, for_misskey: misskey_software?), content_type: 'application/activity+json', serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter, cancel_cache: misskey_software?
|
||||||
end
|
end
|
||||||
|
|
||||||
def embed
|
def embed
|
||||||
|
@ -76,6 +76,29 @@ class StatusesController < ApplicationController
|
||||||
@instance_presenter = InstancePresenter.new
|
@instance_presenter = InstancePresenter.new
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def misskey_software?
|
||||||
|
return @misskey_software if defined?(@misskey_software)
|
||||||
|
|
||||||
|
@misskey_software = false
|
||||||
|
|
||||||
|
return false if !@status.local? || signed_request_account&.domain.blank?
|
||||||
|
|
||||||
|
info = InstanceInfo.find_by(domain: signed_request_account.domain)
|
||||||
|
return false if info.nil?
|
||||||
|
|
||||||
|
@misskey_software = %w(misskey calckey cherrypick sharkey).include?(info.software) &&
|
||||||
|
((@status.public_unlisted_visibility? && @status.account.user&.setting_reject_public_unlisted_subscription) ||
|
||||||
|
(@status.unlisted_visibility? && @status.account.user&.setting_reject_unlisted_subscription))
|
||||||
|
end
|
||||||
|
|
||||||
|
def status_activity_serializer
|
||||||
|
if misskey_software?
|
||||||
|
ActivityPub::NoteForMisskeySerializer
|
||||||
|
else
|
||||||
|
ActivityPub::NoteSerializer
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def redirect_to_original
|
def redirect_to_original
|
||||||
redirect_to(ActivityPub::TagManager.instance.url_for(@status.reblog), allow_other_host: true) if @status.reblog?
|
redirect_to(ActivityPub::TagManager.instance.url_for(@status.reblog), allow_other_host: true) if @status.reblog?
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,7 +21,7 @@ module WellKnown
|
||||||
username = username_from_resource
|
username = username_from_resource
|
||||||
|
|
||||||
@account = begin
|
@account = begin
|
||||||
if username == Rails.configuration.x.local_domain
|
if username == Rails.configuration.x.local_domain || username == Rails.configuration.x.web_domain
|
||||||
Account.representative
|
Account.representative
|
||||||
else
|
else
|
||||||
Account.find_local!(username)
|
Account.find_local!(username)
|
||||||
|
|
|
@ -155,8 +155,8 @@ module JsonLdHelper
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_resource(uri, id, on_behalf_of = nil)
|
def fetch_resource(uri, id_is_known, on_behalf_of = nil, request_options: {})
|
||||||
unless id
|
unless id_is_known
|
||||||
json = fetch_resource_without_id_validation(uri, on_behalf_of)
|
json = fetch_resource_without_id_validation(uri, on_behalf_of)
|
||||||
|
|
||||||
return if !json.is_a?(Hash) || unsupported_uri_scheme?(json['id'])
|
return if !json.is_a?(Hash) || unsupported_uri_scheme?(json['id'])
|
||||||
|
@ -164,17 +164,29 @@ module JsonLdHelper
|
||||||
uri = json['id']
|
uri = json['id']
|
||||||
end
|
end
|
||||||
|
|
||||||
json = fetch_resource_without_id_validation(uri, on_behalf_of)
|
json = fetch_resource_without_id_validation(uri, on_behalf_of, request_options: request_options)
|
||||||
json.present? && json['id'] == uri ? json : nil
|
json.present? && json['id'] == uri ? json : nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_temporary_error = false)
|
def fetch_resource_without_id_validation(uri, on_behalf_of = nil, raise_on_temporary_error = false, request_options: {})
|
||||||
on_behalf_of ||= Account.representative
|
on_behalf_of ||= Account.representative
|
||||||
|
|
||||||
build_request(uri, on_behalf_of).perform do |response|
|
build_request(uri, on_behalf_of, options: request_options).perform do |response|
|
||||||
raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response) || !raise_on_temporary_error
|
raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response) || !raise_on_temporary_error
|
||||||
|
|
||||||
body_to_json(response.body_with_limit) if response.code == 200
|
body_to_json(response.body_with_limit) if response.code == 200 && valid_activitypub_content_type?(response)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid_activitypub_content_type?(response)
|
||||||
|
return true if response.mime_type == 'application/activity+json'
|
||||||
|
|
||||||
|
# When the mime type is `application/ld+json`, we need to check the profile,
|
||||||
|
# but `http.rb` does not parse it for us.
|
||||||
|
return false unless response.mime_type == 'application/ld+json'
|
||||||
|
|
||||||
|
response.headers[HTTP::Headers::CONTENT_TYPE]&.split(';')&.map(&:strip)&.any? do |str|
|
||||||
|
str.start_with?('profile="') && str[9...-1].split.include?('https://www.w3.org/ns/activitystreams')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -204,8 +216,8 @@ module JsonLdHelper
|
||||||
response.code == 501 || ((400...500).cover?(response.code) && ![401, 408, 429].include?(response.code))
|
response.code == 501 || ((400...500).cover?(response.code) && ![401, 408, 429].include?(response.code))
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_request(uri, on_behalf_of = nil)
|
def build_request(uri, on_behalf_of = nil, options: {})
|
||||||
Request.new(:get, uri).tap do |request|
|
Request.new(:get, uri, **options).tap do |request|
|
||||||
request.on_behalf_of(on_behalf_of) if on_behalf_of
|
request.on_behalf_of(on_behalf_of) if on_behalf_of
|
||||||
request.add_headers('Accept' => 'application/activity+json, application/ld+json')
|
request.add_headers('Accept' => 'application/activity+json, application/ld+json')
|
||||||
end
|
end
|
||||||
|
|
|
@ -254,6 +254,7 @@ module LanguagesHelper
|
||||||
|
|
||||||
def valid_locale_or_nil(str)
|
def valid_locale_or_nil(str)
|
||||||
return if str.blank?
|
return if str.blank?
|
||||||
|
return str if valid_locale?(str)
|
||||||
|
|
||||||
code, = str.to_s.split(/[_-]/) # Strip out the region from e.g. en_US or ja-JP
|
code, = str.to_s.split(/[_-]/) # Strip out the region from e.g. en_US or ja-JP
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ export function changeSetting(path, value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const debouncedSave = debounce((dispatch, getState) => {
|
const debouncedSave = debounce((dispatch, getState) => {
|
||||||
if (getState().getIn(['settings', 'saved'])) {
|
if (getState().getIn(['settings', 'saved']) || !getState().getIn(['meta', 'me'])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -124,7 +124,7 @@ class ReportReasonSelector extends PureComponent {
|
||||||
|
|
||||||
api().put(`/api/v1/admin/reports/${id}`, {
|
api().put(`/api/v1/admin/reports/${id}`, {
|
||||||
category,
|
category,
|
||||||
rule_ids,
|
rule_ids: category === 'violation' ? rule_ids : [],
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unsafe-call,
|
||||||
|
@typescript-eslint/no-unsafe-return,
|
||||||
|
@typescript-eslint/no-unsafe-assignment,
|
||||||
|
@typescript-eslint/no-unsafe-member-access
|
||||||
|
-- the settings store is not yet typed */
|
||||||
import type { PropsWithChildren } from 'react';
|
import type { PropsWithChildren } from 'react';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState, useEffect } from 'react';
|
||||||
|
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { changeSetting } from 'mastodon/actions/settings';
|
||||||
import { bannerSettings } from 'mastodon/settings';
|
import { bannerSettings } from 'mastodon/settings';
|
||||||
|
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||||
|
|
||||||
import { IconButton } from './icon_button';
|
import { IconButton } from './icon_button';
|
||||||
|
|
||||||
|
@ -19,13 +26,25 @@ export const DismissableBanner: React.FC<PropsWithChildren<Props>> = ({
|
||||||
id,
|
id,
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const [visible, setVisible] = useState(!bannerSettings.get(id));
|
const dismissed = useAppSelector((state) =>
|
||||||
|
state.settings.getIn(['dismissed_banners', id], false),
|
||||||
|
);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const [visible, setVisible] = useState(!bannerSettings.get(id) && !dismissed);
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const handleDismiss = useCallback(() => {
|
const handleDismiss = useCallback(() => {
|
||||||
setVisible(false);
|
setVisible(false);
|
||||||
bannerSettings.set(id, true);
|
bannerSettings.set(id, true);
|
||||||
}, [id]);
|
dispatch(changeSetting(['dismissed_banners', id], true));
|
||||||
|
}, [id, dispatch]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!visible && !dismissed) {
|
||||||
|
dispatch(changeSetting(['dismissed_banners', id], true));
|
||||||
|
}
|
||||||
|
}, [id, dispatch, visible, dismissed]);
|
||||||
|
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -40,6 +40,7 @@ class DropdownMenu extends PureComponent {
|
||||||
if (this.node && !this.node.contains(e.target)) {
|
if (this.node && !this.node.contains(e.target)) {
|
||||||
this.props.onClose();
|
this.props.onClose();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -156,6 +156,7 @@ class PrivacyDropdown extends PureComponent {
|
||||||
value: PropTypes.string.isRequired,
|
value: PropTypes.string.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
noDirect: PropTypes.bool,
|
noDirect: PropTypes.bool,
|
||||||
|
noLimited: PropTypes.bool,
|
||||||
container: PropTypes.func,
|
container: PropTypes.func,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
|
@ -249,6 +250,10 @@ class PrivacyDropdown extends PureComponent {
|
||||||
if (this.props.noDirect) {
|
if (this.props.noDirect) {
|
||||||
this.selectableOptions = this.selectableOptions.filter((opt) => opt.value !== 'direct');
|
this.selectableOptions = this.selectableOptions.filter((opt) => opt.value !== 'direct');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.props.noLimited) {
|
||||||
|
this.selectableOptions = this.selectableOptions.filter((opt) => !['mutual', 'circle'].includes(opt.value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setTargetRef = c => {
|
setTargetRef = c => {
|
||||||
|
|
|
@ -277,6 +277,7 @@ class Search extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
_calculateOptions (value) {
|
_calculateOptions (value) {
|
||||||
|
const { signedIn } = this.context.identity;
|
||||||
const trimmedValue = value.trim();
|
const trimmedValue = value.trim();
|
||||||
const options = [];
|
const options = [];
|
||||||
|
|
||||||
|
@ -301,7 +302,7 @@ class Search extends PureComponent {
|
||||||
|
|
||||||
const couldBeStatusSearch = searchEnabled;
|
const couldBeStatusSearch = searchEnabled;
|
||||||
|
|
||||||
if (couldBeStatusSearch) {
|
if (couldBeStatusSearch && signedIn) {
|
||||||
options.push({ key: 'status-search', label: <FormattedMessage id='search.quick_action.status_search' defaultMessage='Posts matching {x}' values={{ x: <mark>{trimmedValue}</mark> }} />, action: this.handleStatusSearch });
|
options.push({ key: 'status-search', label: <FormattedMessage id='search.quick_action.status_search' defaultMessage='Posts matching {x}' values={{ x: <mark>{trimmedValue}</mark> }} />, action: this.handleStatusSearch });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,7 +379,7 @@ class Search extends PureComponent {
|
||||||
|
|
||||||
<h4><FormattedMessage id='search_popout.options' defaultMessage='Search options' /></h4>
|
<h4><FormattedMessage id='search_popout.options' defaultMessage='Search options' /></h4>
|
||||||
|
|
||||||
{searchEnabled ? (
|
{searchEnabled && signedIn ? (
|
||||||
<div className='search__popout__menu'>
|
<div className='search__popout__menu'>
|
||||||
{this.defaultOptions.map(({ key, label, action }, i) => (
|
{this.defaultOptions.map(({ key, label, action }, i) => (
|
||||||
<button key={key} onMouseDown={action} className={classNames('search__popout__menu__item', { selected: selectedOption === ((options.length || recent.size) + i) })}>
|
<button key={key} onMouseDown={action} className={classNames('search__popout__menu__item', { selected: selectedOption === ((options.length || recent.size) + i) })}>
|
||||||
|
@ -388,7 +389,11 @@ class Search extends PureComponent {
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className='search__popout__menu__message'>
|
<div className='search__popout__menu__message'>
|
||||||
<FormattedMessage id='search_popout.full_text_search_disabled_message' defaultMessage='Not available on {domain}.' values={{ domain }} />
|
{searchEnabled ? (
|
||||||
|
<FormattedMessage id='search_popout.full_text_search_logged_out_message' defaultMessage='Only available when logged in.' />
|
||||||
|
) : (
|
||||||
|
<FormattedMessage id='search_popout.full_text_search_disabled_message' defaultMessage='Not available on {domain}.' values={{ domain }} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { PureComponent } from 'react';
|
||||||
const iconStyle = {
|
const iconStyle = {
|
||||||
height: null,
|
height: null,
|
||||||
lineHeight: '27px',
|
lineHeight: '27px',
|
||||||
width: `${18 * 1.28571429}px`,
|
minWidth: `${18 * 1.28571429}px`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default class TextIconButton extends PureComponent {
|
export default class TextIconButton extends PureComponent {
|
||||||
|
|
|
@ -45,24 +45,20 @@ class Statuses extends PureComponent {
|
||||||
const emptyMessage = <FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' />;
|
const emptyMessage = <FormattedMessage id='empty_column.explore_statuses' defaultMessage='Nothing is trending right now. Check back later!' />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<StatusList
|
||||||
<DismissableBanner id='explore/statuses'>
|
trackScroll
|
||||||
<FormattedMessage id='dismissable_banner.explore_statuses' defaultMessage='These are posts from across the social web that are gaining traction today. Newer posts with more boosts and favorites are ranked higher.' />
|
prepend={<DismissableBanner id='explore/statuses'><FormattedMessage id='dismissable_banner.explore_statuses' defaultMessage='These are posts from across the social web that are gaining traction today. Newer posts with more boosts and favorites are ranked higher.' /></DismissableBanner>}
|
||||||
</DismissableBanner>
|
alwaysPrepend
|
||||||
|
timelineId='explore'
|
||||||
<StatusList
|
statusIds={statusIds}
|
||||||
trackScroll
|
scrollKey='explore-statuses'
|
||||||
timelineId='explore'
|
hasMore={hasMore}
|
||||||
statusIds={statusIds}
|
isLoading={isLoading}
|
||||||
scrollKey='explore-statuses'
|
onLoadMore={this.handleLoadMore}
|
||||||
hasMore={hasMore}
|
emptyMessage={emptyMessage}
|
||||||
isLoading={isLoading}
|
bindToDocument={!multiColumn}
|
||||||
onLoadMore={this.handleLoadMore}
|
withCounters
|
||||||
emptyMessage={emptyMessage}
|
/>
|
||||||
bindToDocument={!multiColumn}
|
|
||||||
withCounters
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -207,7 +207,7 @@ class ListTimeline extends PureComponent {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='setting-toggle'>
|
<div className='setting-toggle'>
|
||||||
<Toggle id={`list-${id}-exclusive`} defaultChecked={isExclusive} onChange={this.onExclusiveToggle} />
|
<Toggle id={`list-${id}-exclusive`} checked={isExclusive} onChange={this.onExclusiveToggle} />
|
||||||
<label htmlFor={`list-${id}-exclusive`} className='setting-toggle__label'>
|
<label htmlFor={`list-${id}-exclusive`} className='setting-toggle__label'>
|
||||||
<FormattedMessage id='lists.exclusive' defaultMessage='Hide these posts from home or STL' />
|
<FormattedMessage id='lists.exclusive' defaultMessage='Hide these posts from home or STL' />
|
||||||
</label>
|
</label>
|
||||||
|
|
|
@ -140,6 +140,7 @@ class BoostModal extends ImmutablePureComponent {
|
||||||
{status.get('visibility_ex') !== 'private' && !status.get('reblogged') && (
|
{status.get('visibility_ex') !== 'private' && !status.get('reblogged') && (
|
||||||
<PrivacyDropdown
|
<PrivacyDropdown
|
||||||
noDirect
|
noDirect
|
||||||
|
noLimited
|
||||||
value={privacy}
|
value={privacy}
|
||||||
container={this._findContainer}
|
container={this._findContainer}
|
||||||
onChange={this.props.onChangeBoostPrivacy}
|
onChange={this.props.onChangeBoostPrivacy}
|
||||||
|
|
|
@ -221,7 +221,7 @@ class FocalPointModal extends ImmutablePureComponent {
|
||||||
const worker = createWorker({
|
const worker = createWorker({
|
||||||
workerPath: tesseractWorkerPath,
|
workerPath: tesseractWorkerPath,
|
||||||
corePath: tesseractCorePath,
|
corePath: tesseractCorePath,
|
||||||
langPath: `${assetHost}/ocr/lang-data/`,
|
langPath: `${assetHost}/ocr/lang-data`,
|
||||||
logger: ({ status, progress }) => {
|
logger: ({ status, progress }) => {
|
||||||
if (status === 'recognizing text') {
|
if (status === 'recognizing text') {
|
||||||
this.setState({ ocrStatus: 'detecting', progress });
|
this.setState({ ocrStatus: 'detecting', progress });
|
||||||
|
|
|
@ -100,7 +100,7 @@ class LinkFooter extends PureComponent {
|
||||||
{DividingCircle}
|
{DividingCircle}
|
||||||
<a href={source_url} rel='noopener noreferrer' target='_blank'><FormattedMessage id='footer.source_code' defaultMessage='View source code' /></a>
|
<a href={source_url} rel='noopener noreferrer' target='_blank'><FormattedMessage id='footer.source_code' defaultMessage='View source code' /></a>
|
||||||
{DividingCircle}
|
{DividingCircle}
|
||||||
<span class='version'>v{version}</span>
|
<span className='version'>v{version}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -66,25 +66,30 @@ class NavigationPanel extends Component {
|
||||||
) : (
|
) : (
|
||||||
<ColumnLink transparent to='/search' icon='search' text={intl.formatMessage(messages.search)} />
|
<ColumnLink transparent to='/search' icon='search' text={intl.formatMessage(messages.search)} />
|
||||||
));
|
));
|
||||||
|
let banner = undefined;
|
||||||
|
|
||||||
|
if(transientSingleColumn)
|
||||||
|
banner = (<div className='switch-to-advanced'>
|
||||||
|
{intl.formatMessage(messages.openedInClassicInterface)}
|
||||||
|
{" "}
|
||||||
|
<a href={`/deck${location.pathname}`} className='switch-to-advanced__toggle'>
|
||||||
|
{intl.formatMessage(messages.advancedInterface)}
|
||||||
|
</a>
|
||||||
|
</div>);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='navigation-panel'>
|
<div className='navigation-panel'>
|
||||||
<div className='navigation-panel__logo'>
|
<div className='navigation-panel__logo'>
|
||||||
<Link to='/' className='column-link column-link--logo'><WordmarkLogo /></Link>
|
<Link to='/' className='column-link column-link--logo'><WordmarkLogo /></Link>
|
||||||
|
{!banner && <hr />}
|
||||||
{transientSingleColumn ? (
|
|
||||||
<div class='switch-to-advanced'>
|
|
||||||
{intl.formatMessage(messages.openedInClassicInterface)}
|
|
||||||
{" "}
|
|
||||||
<a href={`/deck${location.pathname}`} class='switch-to-advanced__toggle'>
|
|
||||||
{intl.formatMessage(messages.advancedInterface)}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<hr />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{banner &&
|
||||||
|
<div class='navigation-panel__banner'>
|
||||||
|
{banner}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
{signedIn && (
|
{signedIn && (
|
||||||
<>
|
<>
|
||||||
<ColumnLink transparent to='/home' icon='home' text={intl.formatMessage(messages.home)} />
|
<ColumnLink transparent to='/home' icon='home' text={intl.formatMessage(messages.home)} />
|
||||||
|
|
|
@ -625,6 +625,7 @@
|
||||||
"searchability.unlisted.short": "Followers and reactionners",
|
"searchability.unlisted.short": "Followers and reactionners",
|
||||||
"search_popout.domain": "domain",
|
"search_popout.domain": "domain",
|
||||||
"search_popout.full_text_search_disabled_message": "Not available on {domain}.",
|
"search_popout.full_text_search_disabled_message": "Not available on {domain}.",
|
||||||
|
"search_popout.full_text_search_logged_out_message": "Only available when logged in.",
|
||||||
"search_popout.language_code": "ISO language code",
|
"search_popout.language_code": "ISO language code",
|
||||||
"search_popout.options": "Search options",
|
"search_popout.options": "Search options",
|
||||||
"search_popout.quick_actions": "Quick actions",
|
"search_popout.quick_actions": "Quick actions",
|
||||||
|
|
|
@ -110,6 +110,15 @@ const initialState = ImmutableMap({
|
||||||
body: '',
|
body: '',
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
dismissed_banners: ImmutableMap({
|
||||||
|
'public_timeline': false,
|
||||||
|
'community_timeline': false,
|
||||||
|
'home.explore_prompt': false,
|
||||||
|
'explore/links': false,
|
||||||
|
'explore/statuses': false,
|
||||||
|
'explore/tags': false,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const defaultColumns = fromJS([
|
const defaultColumns = fromJS([
|
||||||
|
|
|
@ -155,6 +155,10 @@ delegate(document, '#form_admin_settings_enable_bootstrap_timeline_accounts', 'c
|
||||||
const onChangeRegistrationMode = (target) => {
|
const onChangeRegistrationMode = (target) => {
|
||||||
const enabled = target.value === 'approved';
|
const enabled = target.value === 'approved';
|
||||||
|
|
||||||
|
[].forEach.call(document.querySelectorAll('.form_admin_settings_registrations_mode .warning-hint'), (warning_hint) => {
|
||||||
|
warning_hint.style.display = target.value === 'open' ? 'inline' : 'none';
|
||||||
|
});
|
||||||
|
|
||||||
[].forEach.call(document.querySelectorAll('#form_admin_settings_require_invite_text'), (input) => {
|
[].forEach.call(document.querySelectorAll('#form_admin_settings_require_invite_text'), (input) => {
|
||||||
input.disabled = !enabled;
|
input.disabled = !enabled;
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
|
|
|
@ -284,6 +284,7 @@
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
padding: 0 3px;
|
padding: 0 3px;
|
||||||
line-height: 27px;
|
line-height: 27px;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:active,
|
&:active,
|
||||||
|
@ -2303,8 +2304,7 @@ $ui-header-height: 55px;
|
||||||
|
|
||||||
> .scrollable {
|
> .scrollable {
|
||||||
background: $ui-base-color;
|
background: $ui-base-color;
|
||||||
border-bottom-left-radius: 4px;
|
border-radius: 0 0 4px 4px;
|
||||||
border-bottom-right-radius: 4px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2530,6 +2530,7 @@ $ui-header-height: 55px;
|
||||||
|
|
||||||
.navigation-panel__sign-in-banner,
|
.navigation-panel__sign-in-banner,
|
||||||
.navigation-panel__logo,
|
.navigation-panel__logo,
|
||||||
|
.navigation-panel__banner,
|
||||||
.getting-started__trends {
|
.getting-started__trends {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -4690,11 +4691,6 @@ a.status-card {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
@supports (display: grid) {
|
|
||||||
// hack to fix Chrome <57
|
|
||||||
contain: strict;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > span {
|
& > span {
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,12 +29,12 @@ class AccountStatusesFilter
|
||||||
available_searchabilities = [:public, :unlisted, :private, :direct, :limited, nil]
|
available_searchabilities = [:public, :unlisted, :private, :direct, :limited, nil]
|
||||||
available_visibilities = [:public, :public_unlisted, :login, :unlisted, :private, :direct, :limited]
|
available_visibilities = [:public, :public_unlisted, :login, :unlisted, :private, :direct, :limited]
|
||||||
|
|
||||||
available_searchabilities = [:public] if domain_block&.reject_send_not_public_searchability
|
available_visibilities -= [:public_unlisted] if (domain_block&.detect_invalid_subscription || misskey_software?) && @account.user&.setting_reject_public_unlisted_subscription
|
||||||
available_visibilities -= [:public_unlisted] if domain_block&.reject_send_public_unlisted || (domain_block&.detect_invalid_subscription && @account.user&.setting_reject_public_unlisted_subscription)
|
available_visibilities -= [:unlisted] if (domain_block&.detect_invalid_subscription || misskey_software?) && @account.user&.setting_reject_unlisted_subscription
|
||||||
available_visibilities -= [:unlisted] if domain_block&.detect_invalid_subscription && @account.user&.setting_reject_unlisted_subscription
|
|
||||||
available_visibilities -= [:login] if current_account.nil?
|
available_visibilities -= [:login] if current_account.nil?
|
||||||
|
|
||||||
scope.merge!(scope.where(spoiler_text: ['', nil])) if domain_block&.reject_send_sensitive
|
scope.merge!(scope.where(sensitive: false)) if domain_block&.reject_send_sensitive
|
||||||
|
|
||||||
scope.merge!(scope.where(searchability: available_searchabilities))
|
scope.merge!(scope.where(searchability: available_searchabilities))
|
||||||
scope.merge!(scope.where(visibility: available_visibilities))
|
scope.merge!(scope.where(visibility: available_visibilities))
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ class AccountStatusesFilter
|
||||||
private
|
private
|
||||||
|
|
||||||
def initial_scope
|
def initial_scope
|
||||||
if (suspended? || (domain_block&.reject_send_dissubscribable && @account.dissubscribable)) || domain_block&.reject_send_media || blocked?
|
if suspended? || blocked?
|
||||||
Status.none
|
Status.none
|
||||||
elsif anonymous?
|
elsif anonymous?
|
||||||
account.statuses.where(visibility: %i(public unlisted public_unlisted))
|
account.statuses.where(visibility: %i(public unlisted public_unlisted))
|
||||||
|
@ -156,6 +156,21 @@ class AccountStatusesFilter
|
||||||
end
|
end
|
||||||
|
|
||||||
def domain_block
|
def domain_block
|
||||||
@domain_block = DomainBlock.find_by(domain: @account&.domain)
|
return nil if @current_account.nil? || @current_account.local?
|
||||||
|
|
||||||
|
@domain_block = DomainBlock.find_by(domain: @current_account.domain)
|
||||||
|
end
|
||||||
|
|
||||||
|
def misskey_software?
|
||||||
|
return false if @account.nil? || @account.local?
|
||||||
|
return false if instance_info.nil?
|
||||||
|
|
||||||
|
%w(misskey cherrypick).include?(instance_info.software)
|
||||||
|
end
|
||||||
|
|
||||||
|
def instance_info
|
||||||
|
return @instance_info if defined?(@instance_info)
|
||||||
|
|
||||||
|
@instance_info = InstanceInfo.find_by(domain: @account.domain)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -154,7 +154,7 @@ class ActivityPub::Activity
|
||||||
if object_uri.start_with?('http')
|
if object_uri.start_with?('http')
|
||||||
return if ActivityPub::TagManager.instance.local_uri?(object_uri)
|
return if ActivityPub::TagManager.instance.local_uri?(object_uri)
|
||||||
|
|
||||||
ActivityPub::FetchRemoteStatusService.new.call(object_uri, id: true, on_behalf_of: @account.followers.local.first, request_id: @options[:request_id])
|
ActivityPub::FetchRemoteStatusService.new.call(object_uri, on_behalf_of: @account.followers.local.first, request_id: @options[:request_id])
|
||||||
elsif @object['url'].present?
|
elsif @object['url'].present?
|
||||||
::FetchRemoteStatusService.new.call(@object['url'], request_id: @options[:request_id])
|
::FetchRemoteStatusService.new.call(@object['url'], request_id: @options[:request_id])
|
||||||
end
|
end
|
||||||
|
|
|
@ -116,7 +116,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||||
def find_existing_status
|
def find_existing_status
|
||||||
status = status_from_uri(object_uri)
|
status = status_from_uri(object_uri)
|
||||||
status ||= Status.find_by(uri: @object['atomUri']) if @object['atomUri'].present?
|
status ||= Status.find_by(uri: @object['atomUri']) if @object['atomUri'].present?
|
||||||
status
|
status if status&.account_id == @account.id
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_status_params
|
def process_status_params
|
||||||
|
@ -365,13 +365,15 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||||
|
|
||||||
def fetch_replies(status)
|
def fetch_replies(status)
|
||||||
collection = @object['replies']
|
collection = @object['replies']
|
||||||
return if collection.nil?
|
return if collection.blank?
|
||||||
|
|
||||||
replies = ActivityPub::FetchRepliesService.new.call(status, collection, allow_synchronous_requests: false, request_id: @options[:request_id])
|
replies = ActivityPub::FetchRepliesService.new.call(status, collection, allow_synchronous_requests: false, request_id: @options[:request_id])
|
||||||
return unless replies.nil?
|
return unless replies.nil?
|
||||||
|
|
||||||
uri = value_or_id(collection)
|
uri = value_or_id(collection)
|
||||||
ActivityPub::FetchRepliesWorker.perform_async(status.id, uri, { 'request_id' => @options[:request_id] }) unless uri.nil?
|
ActivityPub::FetchRepliesWorker.perform_async(status.id, uri, { 'request_id' => @options[:request_id] }) unless uri.nil?
|
||||||
|
rescue => e
|
||||||
|
Rails.logger.warn "Error fetching replies: #{e}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def conversation_from_uri(uri)
|
def conversation_from_uri(uri)
|
||||||
|
@ -505,15 +507,15 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||||
end
|
end
|
||||||
|
|
||||||
def searchability_from_audience
|
def searchability_from_audience
|
||||||
if audience_searchable_by.nil?
|
return nil if audience_searchable_by.blank?
|
||||||
nil
|
|
||||||
elsif audience_searchable_by.any? { |uri| ActivityPub::TagManager.instance.public_collection?(uri) }
|
if audience_searchable_by.any? { |uri| ActivityPub::TagManager.instance.public_collection?(uri) }
|
||||||
:public
|
:public
|
||||||
elsif audience_searchable_by.include?('kmyblue:Limited') || audience_searchable_by.include?('as:Limited')
|
elsif audience_searchable_by.include?('kmyblue:Limited') || audience_searchable_by.include?('as:Limited')
|
||||||
:limited
|
:limited
|
||||||
elsif audience_searchable_by.include?(@account.followers_url)
|
elsif audience_searchable_by.include?(@account.followers_url)
|
||||||
:private
|
:private
|
||||||
else
|
elsif audience_searchable_by.include?(@account.uri) || audience_searchable_by.include?(@account.url)
|
||||||
:direct
|
:direct
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,6 +20,6 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
|
||||||
serialized_hash = serialized_hash.select { |k, _| options[:fields].include?(k) } if options[:fields]
|
serialized_hash = serialized_hash.select { |k, _| options[:fields].include?(k) } if options[:fields]
|
||||||
serialized_hash = self.class.transform_key_casing!(serialized_hash, instance_options)
|
serialized_hash = self.class.transform_key_casing!(serialized_hash, instance_options)
|
||||||
|
|
||||||
{ '@context' => serialized_context(named_contexts, context_extensions) }.merge(serialized_hash)
|
{ '@context': serialized_context(named_contexts, context_extensions) }.merge(serialized_hash)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,6 +21,8 @@ module ActivityPub::CaseTransform
|
||||||
value
|
value
|
||||||
elsif value.start_with?('_:')
|
elsif value.start_with?('_:')
|
||||||
"_:#{value.delete_prefix('_:').underscore.camelize(:lower)}"
|
"_:#{value.delete_prefix('_:').underscore.camelize(:lower)}"
|
||||||
|
elsif LanguagesHelper::ISO_639_1_REGIONAL.key?(value.to_sym) # rubocop:disable Lint/DuplicateBranch
|
||||||
|
value
|
||||||
else
|
else
|
||||||
value.underscore.camelize(:lower)
|
value.underscore.camelize(:lower)
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,6 +4,7 @@ class ActivityPub::LinkedDataSignature
|
||||||
include JsonLdHelper
|
include JsonLdHelper
|
||||||
|
|
||||||
CONTEXT = 'https://w3id.org/identity/v1'
|
CONTEXT = 'https://w3id.org/identity/v1'
|
||||||
|
SIGNATURE_CONTEXT = 'https://w3id.org/security/v1'
|
||||||
|
|
||||||
def initialize(json)
|
def initialize(json)
|
||||||
@json = json.with_indifferent_access
|
@json = json.with_indifferent_access
|
||||||
|
@ -18,8 +19,8 @@ class ActivityPub::LinkedDataSignature
|
||||||
|
|
||||||
return unless type == 'RsaSignature2017'
|
return unless type == 'RsaSignature2017'
|
||||||
|
|
||||||
creator = ActivityPub::TagManager.instance.uri_to_actor(creator_uri)
|
creator = ActivityPub::TagManager.instance.uri_to_actor(creator_uri)
|
||||||
creator ||= ActivityPub::FetchRemoteKeyService.new.call(creator_uri, id: false)
|
creator = ActivityPub::FetchRemoteKeyService.new.call(creator_uri) if creator&.public_key.blank?
|
||||||
|
|
||||||
return if creator.nil?
|
return if creator.nil?
|
||||||
|
|
||||||
|
@ -28,6 +29,8 @@ class ActivityPub::LinkedDataSignature
|
||||||
to_be_verified = options_hash + document_hash
|
to_be_verified = options_hash + document_hash
|
||||||
|
|
||||||
creator if creator.keypair.public_key.verify(OpenSSL::Digest.new('SHA256'), Base64.decode64(signature), to_be_verified)
|
creator if creator.keypair.public_key.verify(OpenSSL::Digest.new('SHA256'), Base64.decode64(signature), to_be_verified)
|
||||||
|
rescue OpenSSL::PKey::RSAError
|
||||||
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
def sign!(creator, sign_with: nil)
|
def sign!(creator, sign_with: nil)
|
||||||
|
@ -44,7 +47,13 @@ class ActivityPub::LinkedDataSignature
|
||||||
|
|
||||||
signature = Base64.strict_encode64(keypair.sign(OpenSSL::Digest.new('SHA256'), to_be_signed))
|
signature = Base64.strict_encode64(keypair.sign(OpenSSL::Digest.new('SHA256'), to_be_signed))
|
||||||
|
|
||||||
@json.merge('signature' => options.merge('signatureValue' => signature))
|
# Mastodon's context is either an array or a single URL
|
||||||
|
context_with_security = Array(@json['@context'])
|
||||||
|
context_with_security << 'https://w3id.org/security/v1'
|
||||||
|
context_with_security.uniq!
|
||||||
|
context_with_security = context_with_security.first if context_with_security.size == 1
|
||||||
|
|
||||||
|
@json.merge('signature' => options.merge('signatureValue' => signature), '@context' => context_with_security)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
class ActivityPub::Parser::StatusParser
|
class ActivityPub::Parser::StatusParser
|
||||||
include JsonLdHelper
|
include JsonLdHelper
|
||||||
|
|
||||||
|
NORMALIZED_LOCALE_NAMES = LanguagesHelper::SUPPORTED_LOCALES.keys.index_by(&:downcase).freeze
|
||||||
|
|
||||||
# @param [Hash] json
|
# @param [Hash] json
|
||||||
# @param [Hash] magic_values
|
# @param [Hash] magic_values
|
||||||
# @option magic_values [String] :followers_collection
|
# @option magic_values [String] :followers_collection
|
||||||
|
@ -53,7 +55,8 @@ class ActivityPub::Parser::StatusParser
|
||||||
end
|
end
|
||||||
|
|
||||||
def created_at
|
def created_at
|
||||||
@object['published']&.to_datetime
|
datetime = @object['published']&.to_datetime
|
||||||
|
datetime if datetime.present? && (0..9999).cover?(datetime.year)
|
||||||
rescue ArgumentError
|
rescue ArgumentError
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
@ -98,6 +101,13 @@ class ActivityPub::Parser::StatusParser
|
||||||
end
|
end
|
||||||
|
|
||||||
def language
|
def language
|
||||||
|
lang = raw_language_code
|
||||||
|
lang.presence && NORMALIZED_LOCALE_NAMES.fetch(lang.downcase.to_sym, lang)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def raw_language_code
|
||||||
if content_language_map?
|
if content_language_map?
|
||||||
@object['contentMap'].keys.first
|
@object['contentMap'].keys.first
|
||||||
elsif name_language_map?
|
elsif name_language_map?
|
||||||
|
@ -107,8 +117,6 @@ class ActivityPub::Parser::StatusParser
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def audience_to
|
def audience_to
|
||||||
as_array(@object['to'] || @json['to']).map { |x| value_or_id(x) }
|
as_array(@object['to'] || @json['to']).map { |x| value_or_id(x) }
|
||||||
end
|
end
|
||||||
|
|
|
@ -119,10 +119,7 @@ class ActivityPub::TagManager
|
||||||
end.compact
|
end.compact
|
||||||
end
|
end
|
||||||
when 'limited'
|
when 'limited'
|
||||||
status.mentions.each_with_object([]) do |mention, result|
|
['kmyblue:Limited'] # to avoid Fedibird personal visibility
|
||||||
result << uri_for(mention.account)
|
|
||||||
result << followers_uri_for(mention.account) if mention.account.group?
|
|
||||||
end.compact
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -240,12 +237,10 @@ class ActivityPub::TagManager
|
||||||
[COLLECTIONS[:public]]
|
[COLLECTIONS[:public]]
|
||||||
when 'private'
|
when 'private'
|
||||||
[account_followers_url(status.account)]
|
[account_followers_url(status.account)]
|
||||||
when 'direct'
|
|
||||||
status.conversation_id.present? ? [uri_for(status.conversation)] : []
|
|
||||||
when 'limited'
|
when 'limited'
|
||||||
['as:Limited', 'kmyblue:Limited']
|
['as:Limited', 'kmyblue:Limited']
|
||||||
else
|
else
|
||||||
[]
|
status.conversation_id.present? ? [uri_for(status.conversation), account_url(status.account)] : [account_url(status.account)]
|
||||||
end
|
end
|
||||||
|
|
||||||
searchable_by.concat(mentions_uris(status)).compact
|
searchable_by.concat(mentions_uris(status)).compact
|
||||||
|
@ -260,7 +255,7 @@ class ActivityPub::TagManager
|
||||||
when 'limited'
|
when 'limited'
|
||||||
['as:Limited', 'kmyblue:Limited']
|
['as:Limited', 'kmyblue:Limited']
|
||||||
else
|
else
|
||||||
[]
|
[account_url(account)]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -14,14 +14,16 @@ class Admin::SystemCheck::SoftwareVersionCheck < Admin::SystemCheck::BaseCheck
|
||||||
def message
|
def message
|
||||||
if software_updates.any?(&:urgent?)
|
if software_updates.any?(&:urgent?)
|
||||||
Admin::SystemCheck::Message.new(:software_version_critical_check, nil, admin_software_updates_path, true)
|
Admin::SystemCheck::Message.new(:software_version_critical_check, nil, admin_software_updates_path, true)
|
||||||
else
|
elsif software_updates.any?(&:patch_type?)
|
||||||
Admin::SystemCheck::Message.new(:software_version_patch_check, nil, admin_software_updates_path)
|
Admin::SystemCheck::Message.new(:software_version_patch_check, nil, admin_software_updates_path)
|
||||||
|
else
|
||||||
|
Admin::SystemCheck::Message.new(:software_version_check, nil, admin_software_updates_path)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def software_updates
|
def software_updates
|
||||||
@software_updates ||= SoftwareUpdate.pending_to_a.filter { |update| update.urgent? || update.patch_type? }
|
@software_updates ||= SoftwareUpdate.pending_to_a
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,14 +4,36 @@ module ApplicationExtension
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
included do
|
included do
|
||||||
|
include Redisable
|
||||||
|
|
||||||
has_many :created_users, class_name: 'User', foreign_key: 'created_by_application_id', inverse_of: :created_by_application
|
has_many :created_users, class_name: 'User', foreign_key: 'created_by_application_id', inverse_of: :created_by_application
|
||||||
|
|
||||||
validates :name, length: { maximum: 60 }
|
validates :name, length: { maximum: 60 }
|
||||||
validates :website, url: true, length: { maximum: 2_000 }, if: :website?
|
validates :website, url: true, length: { maximum: 2_000 }, if: :website?
|
||||||
validates :redirect_uri, length: { maximum: 2_000 }
|
validates :redirect_uri, length: { maximum: 2_000 }
|
||||||
|
|
||||||
|
# The relationship used between Applications and AccessTokens is using
|
||||||
|
# dependent: delete_all, which means the ActiveRecord callback in
|
||||||
|
# AccessTokenExtension is not run, so instead we manually announce to
|
||||||
|
# streaming that these tokens are being deleted.
|
||||||
|
before_destroy :close_streaming_sessions, prepend: true
|
||||||
end
|
end
|
||||||
|
|
||||||
def confirmation_redirect_uri
|
def confirmation_redirect_uri
|
||||||
redirect_uri.lines.first.strip
|
redirect_uri.lines.first.strip
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def close_streaming_sessions(resource_owner = nil)
|
||||||
|
# TODO: #28793 Combine into a single topic
|
||||||
|
payload = Oj.dump(event: :kill)
|
||||||
|
scope = access_tokens
|
||||||
|
scope = scope.where(resource_owner_id: resource_owner.id) unless resource_owner.nil?
|
||||||
|
scope.in_batches do |tokens|
|
||||||
|
redis.pipelined do |pipeline|
|
||||||
|
tokens.ids.each do |id|
|
||||||
|
pipeline.publish("timeline:access_token:#{id}", payload)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -75,7 +75,12 @@ class AttachmentBatch
|
||||||
end
|
end
|
||||||
when :fog
|
when :fog
|
||||||
logger.debug { "Deleting #{attachment.path(style)}" }
|
logger.debug { "Deleting #{attachment.path(style)}" }
|
||||||
attachment.directory.files.new(key: attachment.path(style)).destroy
|
|
||||||
|
begin
|
||||||
|
attachment.send(:directory).files.new(key: attachment.path(style)).destroy
|
||||||
|
rescue Fog::Storage::OpenStack::NotFound
|
||||||
|
# Ignore failure to delete a file that has already been deleted
|
||||||
|
end
|
||||||
when :azure
|
when :azure
|
||||||
logger.debug { "Deleting #{attachment.path(style)}" }
|
logger.debug { "Deleting #{attachment.path(style)}" }
|
||||||
attachment.destroy
|
attachment.destroy
|
||||||
|
|
|
@ -594,7 +594,7 @@ class FeedManager
|
||||||
arr = crutches[:active_mentions][s.id] || []
|
arr = crutches[:active_mentions][s.id] || []
|
||||||
arr.push(s.account_id)
|
arr.push(s.account_id)
|
||||||
|
|
||||||
if s.reblog?
|
if s.reblog? && s.reblog.present?
|
||||||
arr.push(s.reblog.account_id)
|
arr.push(s.reblog.account_id)
|
||||||
arr.concat(crutches[:active_mentions][s.reblog_of_id] || [])
|
arr.concat(crutches[:active_mentions][s.reblog_of_id] || [])
|
||||||
end
|
end
|
||||||
|
|
|
@ -42,13 +42,13 @@ class InlineRenderer
|
||||||
private
|
private
|
||||||
|
|
||||||
def preload_associations_for_status
|
def preload_associations_for_status
|
||||||
ActiveRecord::Associations::Preloader.new(records: @object, associations: {
|
ActiveRecord::Associations::Preloader.new(records: [@object], associations: {
|
||||||
active_mentions: :account,
|
active_mentions: :account,
|
||||||
|
|
||||||
reblog: {
|
reblog: {
|
||||||
active_mentions: :account,
|
active_mentions: :account,
|
||||||
},
|
},
|
||||||
})
|
}).call
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_user
|
def current_user
|
||||||
|
|
|
@ -36,7 +36,9 @@ class LinkDetailsExtractor
|
||||||
end
|
end
|
||||||
|
|
||||||
def language
|
def language
|
||||||
json['inLanguage']
|
lang = json['inLanguage']
|
||||||
|
lang = lang.first if lang.is_a?(Array)
|
||||||
|
lang.is_a?(Hash) ? (lang['alternateName'] || lang['name']) : lang
|
||||||
end
|
end
|
||||||
|
|
||||||
def type
|
def type
|
||||||
|
@ -263,16 +265,21 @@ class LinkDetailsExtractor
|
||||||
end
|
end
|
||||||
|
|
||||||
def document
|
def document
|
||||||
@document ||= Nokogiri::HTML(@html, nil, encoding)
|
@document ||= detect_encoding_and_parse_document
|
||||||
end
|
end
|
||||||
|
|
||||||
def encoding
|
def detect_encoding_and_parse_document
|
||||||
@encoding ||= begin
|
[detect_encoding, nil, @html_charset, 'UTF-8'].uniq.each do |encoding|
|
||||||
guess = detector.detect(@html, @html_charset)
|
document = Nokogiri::HTML(@html, nil, encoding)
|
||||||
guess&.fetch(:confidence, 0).to_i > 60 ? guess&.fetch(:encoding, nil) : nil
|
return document if document.to_s.valid_encoding?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def detect_encoding
|
||||||
|
guess = detector.detect(@html, @html_charset)
|
||||||
|
guess&.fetch(:confidence, 0).to_i > 60 ? guess&.fetch(:encoding, nil) : nil
|
||||||
|
end
|
||||||
|
|
||||||
def detector
|
def detector
|
||||||
@detector ||= CharlockHolmes::EncodingDetector.new.tap do |detector|
|
@detector ||= CharlockHolmes::EncodingDetector.new.tap do |detector|
|
||||||
detector.strip_tags = true
|
detector.strip_tags = true
|
||||||
|
|
|
@ -77,6 +77,7 @@ class Request
|
||||||
@url = Addressable::URI.parse(url).normalize
|
@url = Addressable::URI.parse(url).normalize
|
||||||
@http_client = options.delete(:http_client)
|
@http_client = options.delete(:http_client)
|
||||||
@allow_local = options.delete(:allow_local)
|
@allow_local = options.delete(:allow_local)
|
||||||
|
@full_path = !options.delete(:omit_query_string)
|
||||||
@options = options.merge(socket_class: use_proxy? || @allow_local ? ProxySocket : Socket)
|
@options = options.merge(socket_class: use_proxy? || @allow_local ? ProxySocket : Socket)
|
||||||
@options = @options.merge(timeout_class: PerOperationWithDeadline, timeout_options: TIMEOUT)
|
@options = @options.merge(timeout_class: PerOperationWithDeadline, timeout_options: TIMEOUT)
|
||||||
@options = @options.merge(proxy_url) if use_proxy?
|
@options = @options.merge(proxy_url) if use_proxy?
|
||||||
|
@ -146,7 +147,7 @@ class Request
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_common_headers!
|
def set_common_headers!
|
||||||
@headers[REQUEST_TARGET] = "#{@verb} #{@url.path}"
|
@headers[REQUEST_TARGET] = request_target
|
||||||
@headers['User-Agent'] = Mastodon::Version.user_agent
|
@headers['User-Agent'] = Mastodon::Version.user_agent
|
||||||
@headers['Host'] = @url.host
|
@headers['Host'] = @url.host
|
||||||
@headers['Date'] = Time.now.utc.httpdate
|
@headers['Date'] = Time.now.utc.httpdate
|
||||||
|
@ -157,6 +158,14 @@ class Request
|
||||||
@headers['Digest'] = "SHA-256=#{Digest::SHA256.base64digest(@options[:body])}"
|
@headers['Digest'] = "SHA-256=#{Digest::SHA256.base64digest(@options[:body])}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def request_target
|
||||||
|
if @url.query.nil? || !@full_path
|
||||||
|
"#{@verb} #{@url.path}"
|
||||||
|
else
|
||||||
|
"#{@verb} #{@url.path}?#{@url.query}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def signature
|
def signature
|
||||||
algorithm = 'rsa-sha256'
|
algorithm = 'rsa-sha256'
|
||||||
signature = Base64.strict_encode64(@keypair.sign(OpenSSL::Digest.new('SHA256'), signed_string))
|
signature = Base64.strict_encode64(@keypair.sign(OpenSSL::Digest.new('SHA256'), signed_string))
|
||||||
|
|
|
@ -380,7 +380,7 @@ class SearchQueryTransformer < Parslet::Transform
|
||||||
end
|
end
|
||||||
|
|
||||||
rule(clause: subtree(:clause)) do
|
rule(clause: subtree(:clause)) do
|
||||||
prefix = clause[:prefix][:term].to_s if clause[:prefix]
|
prefix = clause[:prefix][:term].to_s.downcase if clause[:prefix]
|
||||||
operator = clause[:operator]&.to_s
|
operator = clause[:operator]&.to_s
|
||||||
term = clause[:phrase] ? clause[:phrase].map { |term| term[:term].to_s }.join(' ') : clause[:term].to_s
|
term = clause[:phrase] ? clause[:phrase].map { |term| term[:term].to_s }.join(' ') : clause[:term].to_s
|
||||||
|
|
||||||
|
|
|
@ -21,43 +21,43 @@ class StatusReachFinder
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def inboxes_for_limited
|
||||||
|
DeliveryFailureTracker.without_unavailable(
|
||||||
|
@status.mentioned_accounts.where.not(domain: nil).pluck(:inbox_url).compact.uniq
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def reached_account_inboxes
|
def reached_account_inboxes
|
||||||
|
Account.where(id: reached_account_ids).where.not(domain: banned_domains).inboxes
|
||||||
|
end
|
||||||
|
|
||||||
|
def reached_account_inboxes_for_misskey
|
||||||
|
Account.where(id: reached_account_ids).where(domain: banned_domains_for_misskey).inboxes
|
||||||
|
end
|
||||||
|
|
||||||
|
def reached_account_ids
|
||||||
# When the status is a reblog, there are no interactions with it
|
# When the status is a reblog, there are no interactions with it
|
||||||
# directly, we assume all interactions are with the original one
|
# directly, we assume all interactions are with the original one
|
||||||
|
|
||||||
if @status.reblog?
|
if @status.reblog?
|
||||||
[]
|
[reblog_of_account_id]
|
||||||
elsif @status.limited_visibility?
|
elsif @status.limited_visibility?
|
||||||
Account.where(id: mentioned_account_ids).where.not(domain: banned_domains).inboxes
|
[mentioned_account_ids]
|
||||||
else
|
else
|
||||||
Account.where(id: reached_account_ids).where.not(domain: banned_domains).inboxes
|
[
|
||||||
end
|
replied_to_account_id,
|
||||||
end
|
reblog_of_account_id,
|
||||||
|
mentioned_account_ids,
|
||||||
def reached_account_inboxes_for_misskey
|
reblogs_account_ids,
|
||||||
if @status.reblog?
|
favourites_account_ids,
|
||||||
[]
|
replies_account_ids,
|
||||||
elsif @status.limited_visibility?
|
].tap do |arr|
|
||||||
Account.where(id: mentioned_account_ids).where(domain: banned_domains_for_misskey).inboxes
|
arr.flatten!
|
||||||
else
|
arr.compact!
|
||||||
Account.where(id: reached_account_ids).where(domain: banned_domains_for_misskey).inboxes
|
arr.uniq!
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def reached_account_ids
|
|
||||||
[
|
|
||||||
replied_to_account_id,
|
|
||||||
reblog_of_account_id,
|
|
||||||
mentioned_account_ids,
|
|
||||||
reblogs_account_ids,
|
|
||||||
favourites_account_ids,
|
|
||||||
replies_account_ids,
|
|
||||||
].tap do |arr|
|
|
||||||
arr.flatten!
|
|
||||||
arr.compact!
|
|
||||||
arr.uniq!
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -137,10 +137,6 @@ class StatusReachFinder
|
||||||
[]
|
[]
|
||||||
else
|
else
|
||||||
blocks = DomainBlock.where(domain: nil)
|
blocks = DomainBlock.where(domain: nil)
|
||||||
blocks = blocks.or(DomainBlock.where(reject_send_not_public_searchability: true)) if status.compute_searchability != 'public'
|
|
||||||
blocks = blocks.or(DomainBlock.where(reject_send_public_unlisted: true)) if status.public_unlisted_visibility?
|
|
||||||
blocks = blocks.or(DomainBlock.where(reject_send_dissubscribable: true)) if status.account.dissubscribable
|
|
||||||
blocks = blocks.or(DomainBlock.where(reject_send_media: true)) if status.with_media?
|
|
||||||
blocks = blocks.or(DomainBlock.where(reject_send_sensitive: true)) if (status.with_media? && status.sensitive) || status.spoiler_text?
|
blocks = blocks.or(DomainBlock.where(reject_send_sensitive: true)) if (status.with_media? && status.sensitive) || status.spoiler_text?
|
||||||
blocks.pluck(:domain).uniq
|
blocks.pluck(:domain).uniq
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Vacuum::ApplicationsVacuum
|
|
||||||
def perform
|
|
||||||
Doorkeeper::Application.where(owner_id: nil)
|
|
||||||
.where.missing(:created_users, :access_tokens, :access_grants)
|
|
||||||
.where(created_at: ...1.day.ago)
|
|
||||||
.in_batches.delete_all
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -4,6 +4,7 @@ class Vacuum::FeedsVacuum
|
||||||
def perform
|
def perform
|
||||||
vacuum_inactive_home_feeds!
|
vacuum_inactive_home_feeds!
|
||||||
vacuum_inactive_list_feeds!
|
vacuum_inactive_list_feeds!
|
||||||
|
vacuum_inactive_antenna_feeds!
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -20,6 +21,12 @@ class Vacuum::FeedsVacuum
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def vacuum_inactive_antenna_feeds!
|
||||||
|
inactive_users_antennas.select(:id).in_batches do |antennas|
|
||||||
|
feed_manager.clean_feeds!(:antenna, antennas.ids)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def inactive_users
|
def inactive_users
|
||||||
User.confirmed.inactive
|
User.confirmed.inactive
|
||||||
end
|
end
|
||||||
|
@ -28,6 +35,10 @@ class Vacuum::FeedsVacuum
|
||||||
List.where(account_id: inactive_users.select(:account_id))
|
List.where(account_id: inactive_users.select(:account_id))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def inactive_users_antennas
|
||||||
|
Antenna.where(account_id: inactive_users.select(:account_id))
|
||||||
|
end
|
||||||
|
|
||||||
def feed_manager
|
def feed_manager
|
||||||
FeedManager.instance
|
FeedManager.instance
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,7 +22,7 @@ class VideoMetadataExtractor
|
||||||
private
|
private
|
||||||
|
|
||||||
def ffmpeg_command_output
|
def ffmpeg_command_output
|
||||||
command = Terrapin::CommandLine.new('ffprobe', '-i :path -print_format :format -show_format -show_streams -show_error -loglevel :loglevel')
|
command = Terrapin::CommandLine.new(Rails.configuration.x.ffprobe_binary, '-i :path -print_format :format -show_format -show_streams -show_error -loglevel :loglevel')
|
||||||
command.run(path: @path, format: 'json', loglevel: 'fatal')
|
command.run(path: @path, format: 'json', loglevel: 'fatal')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -41,8 +41,8 @@ class VideoMetadataExtractor
|
||||||
@colorspace = video_stream[:pix_fmt]
|
@colorspace = video_stream[:pix_fmt]
|
||||||
@width = video_stream[:width]
|
@width = video_stream[:width]
|
||||||
@height = video_stream[:height]
|
@height = video_stream[:height]
|
||||||
@frame_rate = video_stream[:avg_frame_rate] == '0/0' ? nil : Rational(video_stream[:avg_frame_rate])
|
@frame_rate = parse_framerate(video_stream[:avg_frame_rate])
|
||||||
@r_frame_rate = video_stream[:r_frame_rate] == '0/0' ? nil : Rational(video_stream[:r_frame_rate])
|
@r_frame_rate = parse_framerate(video_stream[:r_frame_rate])
|
||||||
# For some video streams the frame_rate reported by `ffprobe` will be 0/0, but for these streams we
|
# For some video streams the frame_rate reported by `ffprobe` will be 0/0, but for these streams we
|
||||||
# should use `r_frame_rate` instead. Video screencast generated by Gnome Screencast have this issue.
|
# should use `r_frame_rate` instead. Video screencast generated by Gnome Screencast have this issue.
|
||||||
@frame_rate ||= @r_frame_rate
|
@frame_rate ||= @r_frame_rate
|
||||||
|
@ -55,4 +55,10 @@ class VideoMetadataExtractor
|
||||||
|
|
||||||
@invalid = true if @metadata.key?(:error)
|
@invalid = true if @metadata.key?(:error)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def parse_framerate(raw)
|
||||||
|
Rational(raw)
|
||||||
|
rescue ZeroDivisionError
|
||||||
|
nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,8 @@ class Webfinger
|
||||||
class RedirectError < Error; end
|
class RedirectError < Error; end
|
||||||
|
|
||||||
class Response
|
class Response
|
||||||
|
ACTIVITYPUB_READY_TYPE = ['application/activity+json', 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'].freeze
|
||||||
|
|
||||||
attr_reader :uri
|
attr_reader :uri
|
||||||
|
|
||||||
def initialize(uri, body)
|
def initialize(uri, body)
|
||||||
|
@ -20,17 +22,28 @@ class Webfinger
|
||||||
end
|
end
|
||||||
|
|
||||||
def link(rel, attribute)
|
def link(rel, attribute)
|
||||||
links.dig(rel, attribute)
|
links.dig(rel, 0, attribute)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self_link_href
|
||||||
|
self_link.fetch('href')
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def links
|
def links
|
||||||
@links ||= @json['links'].index_by { |link| link['rel'] }
|
@links ||= @json.fetch('links', []).group_by { |link| link['rel'] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def self_link
|
||||||
|
links.fetch('self', []).find do |link|
|
||||||
|
ACTIVITYPUB_READY_TYPE.include?(link['type'])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_response!
|
def validate_response!
|
||||||
raise Webfinger::Error, "Missing subject in response for #{@uri}" if subject.blank?
|
raise Webfinger::Error, "Missing subject in response for #{@uri}" if subject.blank?
|
||||||
|
raise Webfinger::Error, "Missing self link in response for #{@uri}" if self_link.blank?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,12 @@ class AdminMailer < ApplicationMailer
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def auto_close_registrations
|
||||||
|
locale_for_account(@me) do
|
||||||
|
mail subject: default_i18n_subject(instance: @instance)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def process_params
|
def process_params
|
||||||
|
|
|
@ -69,8 +69,8 @@ class Account < ApplicationRecord
|
||||||
|
|
||||||
BACKGROUND_REFRESH_INTERVAL = 1.week.freeze
|
BACKGROUND_REFRESH_INTERVAL = 1.week.freeze
|
||||||
|
|
||||||
USERNAME_RE = /[a-z0-9_]+([a-z0-9_.-]+[a-z0-9_]+)?/i
|
USERNAME_RE = /[a-z0-9_]+([.-]+[a-z0-9_]+)*/i
|
||||||
MENTION_RE = %r{(?<![=/[:word:]])@((#{USERNAME_RE})(?:@[[:word:].-]+[[:word:]]+)?)}i
|
MENTION_RE = %r{(?<![=/[:word:]])@((#{USERNAME_RE})(?:@[[:word:]]+([.-]+[[:word:]]+)*)?)}
|
||||||
URL_PREFIX_RE = %r{\Ahttp(s?)://[^/]+}
|
URL_PREFIX_RE = %r{\Ahttp(s?)://[^/]+}
|
||||||
USERNAME_ONLY_RE = /\A#{USERNAME_RE}\z/i
|
USERNAME_ONLY_RE = /\A#{USERNAME_RE}\z/i
|
||||||
|
|
||||||
|
@ -323,13 +323,6 @@ class Account < ApplicationRecord
|
||||||
user&.setting_translatable_private || (settings.present? && settings['translatable_private']) || false
|
user&.setting_translatable_private || (settings.present? && settings['translatable_private']) || false
|
||||||
end
|
end
|
||||||
|
|
||||||
def link_preview?
|
|
||||||
return user.setting_link_preview if local? && user.present?
|
|
||||||
return settings['link_preview'] if settings.present? && settings.key?('link_preview')
|
|
||||||
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
def public_statuses_count
|
def public_statuses_count
|
||||||
hide_statuses_count? ? 0 : statuses_count
|
hide_statuses_count? ? 0 : statuses_count
|
||||||
end
|
end
|
||||||
|
@ -406,7 +399,6 @@ class Account < ApplicationRecord
|
||||||
'hide_following_count' => hide_following_count?,
|
'hide_following_count' => hide_following_count?,
|
||||||
'hide_followers_count' => hide_followers_count?,
|
'hide_followers_count' => hide_followers_count?,
|
||||||
'translatable_private' => translatable_private?,
|
'translatable_private' => translatable_private?,
|
||||||
'link_preview' => link_preview?,
|
|
||||||
}
|
}
|
||||||
if Setting.enable_emoji_reaction
|
if Setting.enable_emoji_reaction
|
||||||
config = config.merge({
|
config = config.merge({
|
||||||
|
|
|
@ -18,16 +18,12 @@ class AccountDomainBlock < ApplicationRecord
|
||||||
belongs_to :account
|
belongs_to :account
|
||||||
validates :domain, presence: true, uniqueness: { scope: :account_id }, domain: true
|
validates :domain, presence: true, uniqueness: { scope: :account_id }, domain: true
|
||||||
|
|
||||||
after_commit :remove_blocking_cache
|
after_commit :invalidate_domain_blocking_cache
|
||||||
after_commit :remove_relationship_cache
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def remove_blocking_cache
|
def invalidate_domain_blocking_cache
|
||||||
Rails.cache.delete("exclude_domains_for:#{account_id}")
|
Rails.cache.delete("exclude_domains_for:#{account_id}")
|
||||||
end
|
Rails.cache.delete(['exclude_domains', account_id, domain])
|
||||||
|
|
||||||
def remove_relationship_cache
|
|
||||||
Rails.cache.delete_matched("relationship:#{account_id}:*")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -78,9 +78,9 @@ class Announcement < ApplicationRecord
|
||||||
else
|
else
|
||||||
scope.select("name, custom_emoji_id, count(*) as count, exists(select 1 from announcement_reactions r where r.account_id = #{account.id} and r.announcement_id = announcement_reactions.announcement_id and r.name = announcement_reactions.name) as me")
|
scope.select("name, custom_emoji_id, count(*) as count, exists(select 1 from announcement_reactions r where r.account_id = #{account.id} and r.announcement_id = announcement_reactions.announcement_id and r.name = announcement_reactions.name) as me")
|
||||||
end
|
end
|
||||||
end
|
end.to_a
|
||||||
|
|
||||||
ActiveRecord::Associations::Preloader.new(records: records, associations: :custom_emoji)
|
ActiveRecord::Associations::Preloader.new(records: records, associations: :custom_emoji).call
|
||||||
records
|
records
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -55,11 +55,15 @@ class Antenna < ApplicationRecord
|
||||||
scope :available_stls, -> { where(available: true, stl: true) }
|
scope :available_stls, -> { where(available: true, stl: true) }
|
||||||
scope :available_ltls, -> { where(available: true, stl: false, ltl: true) }
|
scope :available_ltls, -> { where(available: true, stl: false, ltl: true) }
|
||||||
|
|
||||||
|
validates :title, presence: true
|
||||||
|
|
||||||
validate :list_owner
|
validate :list_owner
|
||||||
validate :validate_limit
|
validate :validate_limit
|
||||||
validate :validate_stl_limit
|
validate :validate_stl_limit
|
||||||
validate :validate_ltl_limit
|
validate :validate_ltl_limit
|
||||||
|
|
||||||
|
before_destroy :clean_feed_manager
|
||||||
|
|
||||||
def list_owner
|
def list_owner
|
||||||
raise Mastodon::ValidationError, I18n.t('antennas.errors.invalid_list_owner') if !list_id.zero? && list.present? && list.account != account
|
raise Mastodon::ValidationError, I18n.t('antennas.errors.invalid_list_owner') if !list_id.zero? && list.present? && list.account != account
|
||||||
end
|
end
|
||||||
|
@ -121,4 +125,8 @@ class Antenna < ApplicationRecord
|
||||||
ltls.any? { |tl| !tl.insert_feeds }
|
ltls.any? { |tl| !tl.insert_feeds }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def clean_feed_manager
|
||||||
|
FeedManager.instance.clean_feeds!(:antenna, [id])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -60,12 +60,6 @@ module AccountInteractions
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def domain_blocking_map(target_account_ids, account_id)
|
|
||||||
accounts_map = Account.where(id: target_account_ids).select('id, domain').each_with_object({}) { |a, h| h[a.id] = a.domain }
|
|
||||||
blocked_domains = domain_blocking_map_by_domain(accounts_map.values.compact, account_id)
|
|
||||||
accounts_map.reduce({}) { |h, (id, domain)| h.merge(id => blocked_domains[domain]) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def domain_blocking_map_by_domain(target_domains, account_id)
|
def domain_blocking_map_by_domain(target_domains, account_id)
|
||||||
follow_mapping(AccountDomainBlock.where(account_id: account_id, domain: target_domains), :domain)
|
follow_mapping(AccountDomainBlock.where(account_id: account_id, domain: target_domains), :domain)
|
||||||
end
|
end
|
||||||
|
@ -191,7 +185,7 @@ module AccountInteractions
|
||||||
end
|
end
|
||||||
|
|
||||||
def unblock_domain!(other_domain)
|
def unblock_domain!(other_domain)
|
||||||
block = domain_blocks.find_by(domain: other_domain)
|
block = domain_blocks.find_by(domain: normalized_domain(other_domain))
|
||||||
block&.destroy
|
block&.destroy
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -339,4 +333,8 @@ module AccountInteractions
|
||||||
def remove_potential_friendship(other_account)
|
def remove_potential_friendship(other_account)
|
||||||
PotentialFriendshipTracker.remove(id, other_account.id)
|
PotentialFriendshipTracker.remove(id, other_account.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def normalized_domain(domain)
|
||||||
|
TagManager.instance.normalize_domain(domain)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -145,7 +145,7 @@ module AccountSearch
|
||||||
tsquery = generate_query_for_search(terms)
|
tsquery = generate_query_for_search(terms)
|
||||||
|
|
||||||
find_by_sql([BASIC_SEARCH_SQL, { limit: limit, offset: offset, tsquery: tsquery }]).tap do |records|
|
find_by_sql([BASIC_SEARCH_SQL, { limit: limit, offset: offset, tsquery: tsquery }]).tap do |records|
|
||||||
ActiveRecord::Associations::Preloader.new(records: records, associations: :account_stat)
|
ActiveRecord::Associations::Preloader.new(records: records, associations: [:account_stat, { user: :role }]).call
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -158,7 +158,7 @@ module AccountSearch
|
||||||
end
|
end
|
||||||
|
|
||||||
find_by_sql([sql_template, { id: account.id, limit: limit, offset: offset, tsquery: tsquery }]).tap do |records|
|
find_by_sql([sql_template, { id: account.id, limit: limit, offset: offset, tsquery: tsquery }]).tap do |records|
|
||||||
ActiveRecord::Associations::Preloader.new(records: records, associations: :account_stat)
|
ActiveRecord::Associations::Preloader.new(records: records, associations: [:account_stat, { user: :role }]).call
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -52,9 +52,13 @@ module Attachmentable
|
||||||
return if attachment.blank? || !/image.*/.match?(attachment.content_type) || attachment.queued_for_write[:original].blank?
|
return if attachment.blank? || !/image.*/.match?(attachment.content_type) || attachment.queued_for_write[:original].blank?
|
||||||
|
|
||||||
width, height = FastImage.size(attachment.queued_for_write[:original].path)
|
width, height = FastImage.size(attachment.queued_for_write[:original].path)
|
||||||
matrix_limit = attachment.content_type == 'image/gif' ? GIF_MATRIX_LIMIT : MAX_MATRIX_LIMIT
|
return unless width.present? && height.present?
|
||||||
|
|
||||||
raise Mastodon::DimensionsValidationError, "#{width}x#{height} images are not supported" if width.present? && height.present? && (width * height > matrix_limit)
|
if attachment.content_type == 'image/gif' && width * height > GIF_MATRIX_LIMIT
|
||||||
|
raise Mastodon::DimensionsValidationError, "#{width}x#{height} GIF files are not supported"
|
||||||
|
elsif width * height > MAX_MATRIX_LIMIT
|
||||||
|
raise Mastodon::DimensionsValidationError, "#{width}x#{height} images are not supported"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def appropriate_extension(attachment)
|
def appropriate_extension(attachment)
|
||||||
|
|
|
@ -14,6 +14,10 @@ module Cacheable
|
||||||
includes(@cache_associated)
|
includes(@cache_associated)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def preload_cacheable_associations(records)
|
||||||
|
ActiveRecord::Associations::Preloader.new(records: records, associations: @cache_associated).call
|
||||||
|
end
|
||||||
|
|
||||||
def cache_ids
|
def cache_ids
|
||||||
select(:id, :updated_at)
|
select(:id, :updated_at)
|
||||||
end
|
end
|
||||||
|
|
|
@ -123,10 +123,6 @@ module HasUserSettings
|
||||||
settings['translatable_private']
|
settings['translatable_private']
|
||||||
end
|
end
|
||||||
|
|
||||||
def setting_link_preview
|
|
||||||
settings['link_preview']
|
|
||||||
end
|
|
||||||
|
|
||||||
def setting_single_ref_to_quote
|
def setting_single_ref_to_quote
|
||||||
settings['single_ref_to_quote']
|
settings['single_ref_to_quote']
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,7 +22,7 @@ module LdapAuthenticable
|
||||||
safe_username = safe_username.gsub(keys, replacement)
|
safe_username = safe_username.gsub(keys, replacement)
|
||||||
end
|
end
|
||||||
|
|
||||||
resource = joins(:account).find_by(accounts: { username: safe_username })
|
resource = joins(:account).merge(Account.where(Account.arel_table[:username].lower.eq safe_username.downcase)).take
|
||||||
|
|
||||||
if resource.blank?
|
if resource.blank?
|
||||||
resource = new(email: attributes[Devise.ldap_mail.to_sym].first, agreement: true, account_attributes: { username: safe_username }, admin: false, external: true, confirmed_at: Time.now.utc)
|
resource = new(email: attributes[Devise.ldap_mail.to_sym].first, agreement: true, account_attributes: { username: safe_username }, admin: false, external: true, confirmed_at: Time.now.utc)
|
||||||
|
|
|
@ -19,17 +19,18 @@ module Omniauthable
|
||||||
end
|
end
|
||||||
|
|
||||||
class_methods do
|
class_methods do
|
||||||
def find_for_oauth(auth, signed_in_resource = nil)
|
def find_for_omniauth(auth, signed_in_resource = nil)
|
||||||
# EOLE-SSO Patch
|
# EOLE-SSO Patch
|
||||||
auth.uid = (auth.uid[0][:uid] || auth.uid[0][:user]) if auth.uid.is_a? Hashie::Array
|
auth.uid = (auth.uid[0][:uid] || auth.uid[0][:user]) if auth.uid.is_a? Hashie::Array
|
||||||
identity = Identity.find_for_oauth(auth)
|
identity = Identity.find_for_omniauth(auth)
|
||||||
|
|
||||||
# If a signed_in_resource is provided it always overrides the existing user
|
# If a signed_in_resource is provided it always overrides the existing user
|
||||||
# to prevent the identity being locked with accidentally created accounts.
|
# to prevent the identity being locked with accidentally created accounts.
|
||||||
# Note that this may leave zombie accounts (with no associated identity) which
|
# Note that this may leave zombie accounts (with no associated identity) which
|
||||||
# can be cleaned up at a later date.
|
# can be cleaned up at a later date.
|
||||||
user = signed_in_resource || identity.user
|
user = signed_in_resource || identity.user
|
||||||
user ||= create_for_oauth(auth)
|
user ||= reattach_for_auth(auth)
|
||||||
|
user ||= create_for_auth(auth)
|
||||||
|
|
||||||
if identity.user.nil?
|
if identity.user.nil?
|
||||||
identity.user = user
|
identity.user = user
|
||||||
|
@ -39,19 +40,35 @@ module Omniauthable
|
||||||
user
|
user
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_for_oauth(auth)
|
private
|
||||||
# Check if the user exists with provided email. If no email was provided,
|
|
||||||
|
def reattach_for_auth(auth)
|
||||||
|
# If allowed, check if a user exists with the provided email address,
|
||||||
|
# and return it if they does not have an associated identity with the
|
||||||
|
# current authentication provider.
|
||||||
|
|
||||||
|
# This can be used to provide a choice of alternative auth providers
|
||||||
|
# or provide smooth gradual transition between multiple auth providers,
|
||||||
|
# but this is discouraged because any insecure provider will put *all*
|
||||||
|
# local users at risk, regardless of which provider they registered with.
|
||||||
|
|
||||||
|
return unless ENV['ALLOW_UNSAFE_AUTH_PROVIDER_REATTACH'] == 'true'
|
||||||
|
|
||||||
|
email, email_is_verified = email_from_auth(auth)
|
||||||
|
return unless email_is_verified
|
||||||
|
|
||||||
|
user = User.find_by(email: email)
|
||||||
|
return if user.nil? || Identity.exists?(provider: auth.provider, user_id: user.id)
|
||||||
|
|
||||||
|
user
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_for_auth(auth)
|
||||||
|
# Create a user for the given auth params. If no email was provided,
|
||||||
# we assign a temporary email and ask the user to verify it on
|
# we assign a temporary email and ask the user to verify it on
|
||||||
# the next step via Auth::SetupController.show
|
# the next step via Auth::SetupController.show
|
||||||
|
|
||||||
strategy = Devise.omniauth_configs[auth.provider.to_sym].strategy
|
email, email_is_verified = email_from_auth(auth)
|
||||||
assume_verified = strategy&.security&.assume_email_is_verified
|
|
||||||
email_is_verified = auth.info.verified || auth.info.verified_email || auth.info.email_verified || assume_verified
|
|
||||||
email = auth.info.verified_email || auth.info.email
|
|
||||||
|
|
||||||
user = User.find_by(email: email) if email_is_verified
|
|
||||||
|
|
||||||
return user unless user.nil?
|
|
||||||
|
|
||||||
user = User.new(user_params_from_auth(email, auth))
|
user = User.new(user_params_from_auth(email, auth))
|
||||||
|
|
||||||
|
@ -66,7 +83,14 @@ module Omniauthable
|
||||||
user
|
user
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
def email_from_auth(auth)
|
||||||
|
strategy = Devise.omniauth_configs[auth.provider.to_sym].strategy
|
||||||
|
assume_verified = strategy&.security&.assume_email_is_verified
|
||||||
|
email_is_verified = auth.info.verified || auth.info.verified_email || auth.info.email_verified || assume_verified
|
||||||
|
email = auth.info.verified_email || auth.info.email
|
||||||
|
|
||||||
|
[email, email_is_verified]
|
||||||
|
end
|
||||||
|
|
||||||
def user_params_from_auth(email, auth)
|
def user_params_from_auth(email, auth)
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,7 +10,7 @@ module RelationshipCacheable
|
||||||
private
|
private
|
||||||
|
|
||||||
def remove_relationship_cache
|
def remove_relationship_cache
|
||||||
Rails.cache.delete("relationship:#{account_id}:#{target_account_id}")
|
Rails.cache.delete(['relationship', account_id, target_account_id])
|
||||||
Rails.cache.delete("relationship:#{target_account_id}:#{account_id}")
|
Rails.cache.delete(['relationship', target_account_id, account_id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
# reject_straight_follow :boolean default(FALSE), not null
|
# reject_straight_follow :boolean default(FALSE), not null
|
||||||
# reject_new_follow :boolean default(FALSE), not null
|
# reject_new_follow :boolean default(FALSE), not null
|
||||||
# hidden :boolean default(FALSE), not null
|
# hidden :boolean default(FALSE), not null
|
||||||
# hidden_anonymous :boolean default(FALSE), not null
|
|
||||||
# detect_invalid_subscription :boolean default(FALSE), not null
|
# detect_invalid_subscription :boolean default(FALSE), not null
|
||||||
# reject_reply_exclude_followers :boolean default(FALSE), not null
|
# reject_reply_exclude_followers :boolean default(FALSE), not null
|
||||||
#
|
#
|
||||||
|
@ -60,10 +59,6 @@ class DomainBlock < ApplicationRecord
|
||||||
reject_favourite? ? :reject_favourite : nil,
|
reject_favourite? ? :reject_favourite : nil,
|
||||||
reject_reply? ? :reject_reply : nil,
|
reject_reply? ? :reject_reply : nil,
|
||||||
reject_reply_exclude_followers? ? :reject_reply_exclude_followers : nil,
|
reject_reply_exclude_followers? ? :reject_reply_exclude_followers : nil,
|
||||||
reject_send_not_public_searchability? ? :reject_send_not_public_searchability : nil,
|
|
||||||
reject_send_public_unlisted? ? :reject_send_public_unlisted : nil,
|
|
||||||
reject_send_dissubscribable? ? :reject_send_dissubscribable : nil,
|
|
||||||
reject_send_media? ? :reject_send_media : nil,
|
|
||||||
reject_send_sensitive? ? :reject_send_sensitive : nil,
|
reject_send_sensitive? ? :reject_send_sensitive : nil,
|
||||||
reject_hashtag? ? :reject_hashtag : nil,
|
reject_hashtag? ? :reject_hashtag : nil,
|
||||||
reject_straight_follow? ? :reject_straight_follow : nil,
|
reject_straight_follow? ? :reject_straight_follow : nil,
|
||||||
|
|
|
@ -28,7 +28,7 @@ class Feed
|
||||||
unhydrated = redis.zrangebyscore(key, "(#{min_id}", "(#{max_id}", limit: [0, limit], with_scores: true).map(&:first).map(&:to_i)
|
unhydrated = redis.zrangebyscore(key, "(#{min_id}", "(#{max_id}", limit: [0, limit], with_scores: true).map(&:first).map(&:to_i)
|
||||||
end
|
end
|
||||||
|
|
||||||
Status.where(id: unhydrated).cache_ids
|
Status.where(id: unhydrated)
|
||||||
end
|
end
|
||||||
|
|
||||||
def key
|
def key
|
||||||
|
|
|
@ -17,7 +17,7 @@ class Identity < ApplicationRecord
|
||||||
validates :uid, presence: true, uniqueness: { scope: :provider }
|
validates :uid, presence: true, uniqueness: { scope: :provider }
|
||||||
validates :provider, presence: true
|
validates :provider, presence: true
|
||||||
|
|
||||||
def self.find_for_oauth(auth)
|
def self.find_for_omniauth(auth)
|
||||||
find_or_create_by(uid: auth.uid, provider: auth.provider)
|
find_or_create_by(uid: auth.uid, provider: auth.provider)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -128,7 +128,7 @@ class Notification < ApplicationRecord
|
||||||
|
|
||||||
# Instead of using the usual `includes`, manually preload each type.
|
# Instead of using the usual `includes`, manually preload each type.
|
||||||
# If polymorphic associations are loaded with the usual `includes`, other types of associations will be loaded more.
|
# If polymorphic associations are loaded with the usual `includes`, other types of associations will be loaded more.
|
||||||
ActiveRecord::Associations::Preloader.new(records: grouped_notifications, associations: associations)
|
ActiveRecord::Associations::Preloader.new(records: grouped_notifications, associations: associations).call
|
||||||
end
|
end
|
||||||
|
|
||||||
unique_target_statuses = notifications.filter_map(&:target_status).uniq
|
unique_target_statuses = notifications.filter_map(&:target_status).uniq
|
||||||
|
|
|
@ -31,7 +31,7 @@ class PublicFeed
|
||||||
# scope.merge!(anonymous_scope) unless account?
|
# scope.merge!(anonymous_scope) unless account?
|
||||||
scope = to_anonymous_scope(scope) unless account?
|
scope = to_anonymous_scope(scope) unless account?
|
||||||
|
|
||||||
scope.cache_ids.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id)
|
scope.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -44,9 +44,9 @@ class Report < ApplicationRecord
|
||||||
delegate :local?, to: :account
|
delegate :local?, to: :account
|
||||||
|
|
||||||
validates :comment, length: { maximum: 1_000 }, if: :local?
|
validates :comment, length: { maximum: 1_000 }, if: :local?
|
||||||
validates :rule_ids, absence: true, unless: :violation?
|
validates :rule_ids, absence: true, if: -> { (category_changed? || rule_ids_changed?) && !violation? }
|
||||||
|
|
||||||
validate :validate_rule_ids
|
validate :validate_rule_ids, if: -> { (category_changed? || rule_ids_changed?) && violation? }
|
||||||
|
|
||||||
# entries here need to be kept in sync with the front-end:
|
# entries here need to be kept in sync with the front-end:
|
||||||
# - app/javascript/mastodon/features/notifications/components/report.jsx
|
# - app/javascript/mastodon/features/notifications/components/report.jsx
|
||||||
|
@ -154,8 +154,6 @@ class Report < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_rule_ids
|
def validate_rule_ids
|
||||||
return unless violation?
|
|
||||||
|
|
||||||
errors.add(:rule_ids, I18n.t('reports.errors.invalid_rules')) unless rules.size == rule_ids&.size
|
errors.add(:rule_ids, I18n.t('reports.errors.invalid_rules')) unless rules.size == rule_ids&.size
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue