diff options
Diffstat (limited to 'Digitigrade')
| -rw-r--r-- | Digitigrade/Job/PushObject.php | 4 | ||||
| -rw-r--r-- | Digitigrade/Model/Instance.php | 2 | ||||
| -rw-r--r-- | Digitigrade/Policy.php | 36 | ||||
| -rw-r--r-- | Digitigrade/Policy/BlockInstance.php | 18 | ||||
| -rw-r--r-- | Digitigrade/Policy/RemoteInstanceInfoSubstitution.php | 33 | ||||
| -rw-r--r-- | Digitigrade/PolicyManager.php | 30 | ||||
| -rw-r--r-- | Digitigrade/Singleton.php | 1 |
7 files changed, 121 insertions, 3 deletions
diff --git a/Digitigrade/Job/PushObject.php b/Digitigrade/Job/PushObject.php index c3d7210..0278eb5 100644 --- a/Digitigrade/Job/PushObject.php +++ b/Digitigrade/Job/PushObject.php @@ -3,6 +3,7 @@ namespace Digitigrade\Job; use Digitigrade\Exception\AuthException; use Digitigrade\Exception\EndpointMissingException; +use Digitigrade\HttpResponseStatus\PolicyRejected; use Digitigrade\Job; use Digitigrade\Logger; use Digitigrade\Model\Instance; @@ -38,6 +39,9 @@ class PushObject extends Job { // start auth now and hopefully by the next retry we'll be able to $instance->beginOutboundAuth(); throw $e; + } catch (PolicyRejected $e) { + $log->warning("can't push to $instance->domain because a policy rejected it. giving up"); + // and don't throw again, pretty much same reasoning as above } } }
\ No newline at end of file diff --git a/Digitigrade/Model/Instance.php b/Digitigrade/Model/Instance.php index 5545849..eecc6ff 100644 --- a/Digitigrade/Model/Instance.php +++ b/Digitigrade/Model/Instance.php @@ -6,6 +6,7 @@ use Digitigrade\Exception\EndpointMissingException; use Digitigrade\GlobalConfig; use Digitigrade\HttpResponseStatus\Unauthorized; use Digitigrade\Job\RefreshOutboundAuthToken; +use Digitigrade\PolicyManager; class Instance extends FetchableModel { public ?int $id; @@ -208,6 +209,7 @@ class Instance extends FetchableModel { if (!$this->auth->outboundPushEnabled) { throw new \RuntimeException("won't push to $this->domain because it hasn't subscribed to us"); } + PolicyManager::getInstance()->checkFederationOrThrow($obj, $this); $context = stream_context_create(['http' => [ 'method' => 'POST', 'header' => "Content-Type: application/json\nAuthorization: Bearer " . $this->auth->outboundToken, diff --git a/Digitigrade/Policy.php b/Digitigrade/Policy.php index 56fbdb0..973c229 100644 --- a/Digitigrade/Policy.php +++ b/Digitigrade/Policy.php @@ -2,6 +2,7 @@ namespace Digitigrade; use Digitigrade\Model\Actor; +use Digitigrade\Model\Instance; use Digitigrade\Model\Interaction; use Digitigrade\Model\Note; @@ -34,4 +35,39 @@ abstract class Policy { } // TODO: public function checkExtension() + + /** + * Checks if an Actor is allowed to be federated to a certain Instance, + * and possibly alters it. + * @param Actor $actor the actor to check (may be modified in-place) + * @param ?Instance $instance the instance that is receiving this (may be null if unknown) + * @return bool true if allowed, false if the request should be cancelled + */ + public function checkActorFederation(Actor $actor, ?Instance $instance): bool { + return true; + } + + /** + * Checks if an Interaction is allowed to be federated to a certain Instance, + * and possibly alters it. + * @param Interaction $interaction the interaction to check (may be modified in-place) + * @param ?Instance $instance the instance that is receiving this (may be null if unknown) + * @return bool true if allowed, false if the request should be cancelled + */ + public function checkInteractionFederation(Interaction $interaction, ?Instance $instance): bool { + return true; + } + + /** + * Checks if an Note is allowed to be federated to a certain Instance, + * and possibly alters it. + * @param Note $note the note to check (may be modified in-place) + * @param ?Instance $instance the instance that is receiving this (may be null if unknown) + * @return bool true if allowed, false if the request should be cancelled + */ + public function checkNoteFederation(Note $note, ?Instance $instance): bool { + return true; + } + + // TODO: public function checkExtensionFederation() }
\ No newline at end of file diff --git a/Digitigrade/Policy/BlockInstance.php b/Digitigrade/Policy/BlockInstance.php index 5f902f2..053418c 100644 --- a/Digitigrade/Policy/BlockInstance.php +++ b/Digitigrade/Policy/BlockInstance.php @@ -2,6 +2,7 @@ namespace Digitigrade\Policy; use Digitigrade\Model\Actor; +use Digitigrade\Model\Instance; use Digitigrade\Model\Interaction; use Digitigrade\Model\Note; use Digitigrade\Model\PushableModel; @@ -15,8 +16,11 @@ class BlockInstance extends Policy { $this->blockedHosts = $hosts; } - private function isBlocked(PushableModel $obj): bool { - $objHost = hostname_from_uri($obj->uri); + private function isBlocked(PushableModel|Instance|null $obj): bool { + if ($obj == null) { + return false; + } + $objHost = hostname_from_uri($obj instanceof Instance ? $obj->domain : $obj->uri); foreach ($this->blockedHosts as $host) { if (str_ends_with($objHost, $host)) { return true; @@ -34,4 +38,14 @@ class BlockInstance extends Policy { public function checkNote(Note $note): bool { return !$this->isBlocked($note); } + + public function checkActorFederation(Actor $actor, ?Instance $instance): bool { + return !$this->isBlocked($instance); + } + public function checkInteractionFederation(Interaction $interaction, ?Instance $instance): bool { + return !$this->isBlocked($instance); + } + public function checkNoteFederation(Note $note, ?Instance $instance): bool { + return !$this->isBlocked($instance); + } }
\ No newline at end of file diff --git a/Digitigrade/Policy/RemoteInstanceInfoSubstitution.php b/Digitigrade/Policy/RemoteInstanceInfoSubstitution.php new file mode 100644 index 0000000..cbcecfc --- /dev/null +++ b/Digitigrade/Policy/RemoteInstanceInfoSubstitution.php @@ -0,0 +1,33 @@ +<?php +namespace Digitigrade\Policy; + +use Digitigrade\Model\Instance; +use Digitigrade\Model\Note; +use Digitigrade\Policy; + +class RemoteInstanceInfoSubstitution extends Policy { + public function checkNoteFederation(Note $note, ?Instance $instance): bool { + if ($instance == null) { + return true; + } + + $infos = [ + '%Instance.domain' => $instance->domain, + '%Instance.name' => $instance->name, + '%Instance.description' => $instance->description ?? '', + '%Instance.softwareName' => $instance->softwareName, + '%Instance.softwareDescription' => $instance->softwareDescription ?? '', + '%Instance.softwareHomepage' => $instance->softwareHomepage ?? '', + '%Instance.softwareVersion' => $instance->softwareVersion ?? '' + ]; + + foreach ($infos as $needle => $replacement) { + $note->plainContent = str_replace($needle, $replacement, $note->plainContent); + foreach ($note->formattedContent as $type => $content) { + $note->formattedContent[$type] = str_replace($needle, $replacement, $content); + } + } + + return true; + } +}
\ No newline at end of file diff --git a/Digitigrade/PolicyManager.php b/Digitigrade/PolicyManager.php index 24c2c7f..69e2ab0 100644 --- a/Digitigrade/PolicyManager.php +++ b/Digitigrade/PolicyManager.php @@ -1,8 +1,10 @@ <?php namespace Digitigrade; +use Digitigrade\HttpResponseStatus\PolicyRejected; use Digitigrade\Model\Actor; use Digitigrade\Model\FetchableModel; +use Digitigrade\Model\Instance; use Digitigrade\Model\Interaction; use Digitigrade\Model\Note; @@ -34,6 +36,34 @@ class PolicyManager extends Singleton { }; } + public function checkOrThrow(FetchableModel $object) { + if (!$this->check($object)) { + throw new PolicyRejected(); + } + } + + /** + * Checks if an object is allowed to be sent to a certain Instance by all + * policies, and possibly alters it. + * @param FetchableModel $object the object to check (may be modified in-place) + * @param ?Instance $instance the instance it's being sent to (may be null if unknown) + * @return bool true if allowed, false if the request should be cancelled + */ + public function checkFederation(FetchableModel $object, ?Instance $instance): bool { + return match ($object::class) { + Actor::class => self::arrayAll($this->policies, fn(Policy $p) => $p->checkActorFederation($object, $instance)), + Interaction::class => self::arrayAll($this->policies, fn(Policy $p) => $p->checkInteractionFederation($object, $instance)), + Note::class => self::arrayAll($this->policies, fn(Policy $p) => $p->checkNoteFederation($object, $instance)), + default => true + }; + } + + public function checkFederationOrThrow(FetchableModel $object, ?Instance $instance) { + if (!$this->checkFederation($object, $instance)) { + throw new PolicyRejected(); + } + } + private static function arrayAll(array $arr, callable $test): bool { // i would use array_all but that's php 8.4+ only which i don't have lol foreach ($arr as $item) { diff --git a/Digitigrade/Singleton.php b/Digitigrade/Singleton.php index 23b2e78..a90bb4a 100644 --- a/Digitigrade/Singleton.php +++ b/Digitigrade/Singleton.php @@ -10,7 +10,6 @@ abstract class Singleton { */ public static function getInstance() { if (!isset(self::$instances[static::class])) { - //error_log('making a new ' . static::class); self::$instances[static::class] = new static(); } return self::$instances[static::class]; |
