Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Federation improvements #849

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion common/templatetags/mastodon.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def current_user_relationship(context, target_identity: "APIdentity"):
"rejecting": False,
"status": "",
}
if target_identity and current_identity:
if target_identity and current_identity and not target_identity.restricted:
if current_identity != target_identity:
if current_identity.is_blocking(
target_identity
Expand Down
5 changes: 5 additions & 0 deletions takahe/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ class Migration(migrations.Migration):
),
("state", models.CharField(default="outdated", max_length=100)),
("state_changed", models.DateTimeField(auto_now_add=True)),
("state_next_attempt", models.DateTimeField(blank=True, null=True)),
(
"state_locked_until",
models.DateTimeField(blank=True, db_index=True, null=True),
),
("nodeinfo", models.JSONField(blank=True, null=True)),
("local", models.BooleanField()),
("blocked", models.BooleanField(default=False)),
Expand Down
20 changes: 20 additions & 0 deletions takahe/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,8 @@ class Domain(models.Model):
# state = StateField(DomainStates)
state = models.CharField(max_length=100, default="outdated")
state_changed = models.DateTimeField(auto_now_add=True)
state_next_attempt = models.DateTimeField(blank=True, null=True)
state_locked_until = models.DateTimeField(null=True, blank=True, db_index=True)

# nodeinfo 2.0 detail about the remote server
nodeinfo = models.JSONField(null=True, blank=True)
Expand Down Expand Up @@ -352,6 +354,24 @@ def available_for_user(cls, user):
def __str__(self):
return self.domain

def recursively_blocked(self) -> bool:
"""
Checks for blocks on all right subsets of this domain, except the very
last part of the TLD.

Yes, I know this weirdly lets you block ".co.uk" or whatever, but
people can do that if they want I guess.
"""
# Efficient short-circuit
if self.blocked:
return True
# Build domain list
domain_parts = [self.domain]
while "." in domain_parts[-1]:
domain_parts.append(domain_parts[-1].split(".", 1)[1])
# See if any of those are blocked
return Domain.objects.filter(domain__in=domain_parts, blocked=True).exists()


def upload_store():
return FileSystemStorage(
Expand Down
9 changes: 8 additions & 1 deletion takahe/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ def create_internal_message(message: dict):

@staticmethod
def fetch_remote_identity(handler: str) -> int | None:
d = handler.split("@")[-1]
domain = Domain.objects.filter(domain=d).first()
if domain and domain.recursively_blocked:
return
InboxMessage.create_internal({"type": "FetchIdentity", "handle": handler})

@staticmethod
Expand Down Expand Up @@ -670,7 +674,9 @@ def txt2html(txt: str) -> str:
return FediverseHtmlParser(linebreaks_filter(txt)).html

@staticmethod
def update_state(obj: Post | PostInteraction | Relay | Identity, state: str):
def update_state(
obj: Post | PostInteraction | Relay | Identity | Domain, state: str
):
obj.state = state
obj.state_changed = timezone.now()
obj.state_next_attempt = None
Expand All @@ -696,6 +702,7 @@ def get_neodb_peers():
nodeinfo__protocols__contains="neodb",
nodeinfo__metadata__nodeEnvironment="production",
local=False,
blocked=False,
).values_list("pk", flat=True)
)
cache.set(cache_key, peers, timeout=1800)
Expand Down
66 changes: 65 additions & 1 deletion users/management/commands/user.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from django.core.management.base import BaseCommand
from tqdm import tqdm
import httpx

from users.models import Preference, User
from takahe.models import Identity, Domain
from takahe.utils import Takahe


class Command(BaseCommand):
Expand All @@ -16,6 +19,11 @@ def add_arguments(self, parser):
action="store_true",
help="check and fix integrity for missing data for user models",
)
parser.add_argument(
"--remote",
action="store_true",
help="reset state for remote domains/users with previous connection issues",
)
parser.add_argument(
"--super", action="store", nargs="*", help="list or toggle superuser"
)
Expand All @@ -32,6 +40,8 @@ def handle(self, *args, **options):
self.list(self.users)
if options["integrity"]:
self.integrity()
if options["remote"]:
self.check_remote()
if options["super"] is not None:
self.superuser(options["super"])
if options["staff"] is not None:
Expand All @@ -50,6 +60,7 @@ def list(self, users):

def integrity(self):
count = 0
self.stdout.write("Checking local users")
for user in tqdm(User.objects.filter(is_active=True)):
i = user.identity.takahe_identity
if i.public_key is None:
Expand All @@ -64,7 +75,60 @@ def integrity(self):
if self.fix:
Preference.objects.create(user=user)
count += 1
self.stdout.write(f"{count} issues")

def check_remote(self):
headers = {
"Accept": "application/json,application/activity+json,application/ld+json"
}
with httpx.Client(timeout=0.5) as client:
count = 0
self.stdout.write("Checking remote domains")
for d in tqdm(
Domain.objects.filter(
local=False, blocked=False, state="connection_issue"
)
):
try:
response = client.get(
f"https://{d.domain}/.well-known/nodeinfo",
follow_redirects=True,
headers=headers,
)
if response.status_code == 200 and "json" in response.headers.get(
"content-type", ""
):
count += 1
if self.fix:
Takahe.update_state(d, "outdated")
except Exception:
pass
self.stdout.write(f"{count} issues")
count = 0
self.stdout.write("Checking remote identities")
for i in tqdm(
Identity.objects.filter(
public_key__isnull=True,
local=False,
restriction=0,
state="connection_issue",
)
):
try:
response = client.request(
"get",
i.actor_uri,
headers=headers,
follow_redirects=True,
)
if (
response.status_code == 200
and "json" in response.headers.get("content-type", "")
and "@context" in response.text
):
Takahe.update_state(i, "outdated")
except Exception:
pass
self.stdout.write(f"{count} issues")

def superuser(self, v):
if v == []:
Expand Down
5 changes: 4 additions & 1 deletion users/migrations/0001_initial_0_10.py
Original file line number Diff line number Diff line change
Expand Up @@ -562,9 +562,12 @@ class Migration(migrations.Migration):
field=models.CharField(
choices=[
("en", "English"),
("da", "Danish"),
("de", "German"),
("fr", "French"),
("it", "Italian"),
("zh-hans", "Simplified Chinese"),
("zh-hant", "Traditional Chinese"),
("da", "Danish"),
],
default="en",
max_length=10,
Expand Down
4 changes: 4 additions & 0 deletions users/models/apidentity.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ def handle(self):
else:
return f"{self.username}@{self.domain_name}"

@property
def restricted(self):
return self.takahe_identity.restriction != 2

@property
def following(self):
return Takahe.get_following_ids(self.pk)
Expand Down
Loading