aboutsummaryrefslogtreecommitdiffhomepage
path: root/Digitigrade
diff options
context:
space:
mode:
Diffstat (limited to 'Digitigrade')
-rw-r--r--Digitigrade/Job.php2
-rw-r--r--Digitigrade/Job/RefreshOutboundAuthToken.php29
-rw-r--r--Digitigrade/Job/RequestOutboundAuthToken.php28
-rw-r--r--Digitigrade/Job/UpdateInstanceInfo.php5
-rw-r--r--Digitigrade/JobQueue.php2
-rw-r--r--Digitigrade/Model.php7
-rw-r--r--Digitigrade/Model/FetchableModel.php8
-rw-r--r--Digitigrade/Model/Instance.php69
-rw-r--r--Digitigrade/Model/InstanceAuth.php21
9 files changed, 161 insertions, 10 deletions
diff --git a/Digitigrade/Job.php b/Digitigrade/Job.php
index 4fc799a..146e4b2 100644
--- a/Digitigrade/Job.php
+++ b/Digitigrade/Job.php
@@ -30,7 +30,7 @@ abstract class Job implements \JsonSerializable {
public abstract function run();
/**
- * Submits the job to the global job queue with default options.
+ * Submits the job to the global job queue with appropriate options.
* @return void
*/
public function submit() {
diff --git a/Digitigrade/Job/RefreshOutboundAuthToken.php b/Digitigrade/Job/RefreshOutboundAuthToken.php
new file mode 100644
index 0000000..588617f
--- /dev/null
+++ b/Digitigrade/Job/RefreshOutboundAuthToken.php
@@ -0,0 +1,29 @@
+<?php
+namespace Digitigrade\Job;
+
+use Digitigrade\Job;
+use Digitigrade\JobQueue;
+use Digitigrade\Logger;
+use Digitigrade\Model\Instance;
+
+class RefreshOutboundAuthToken extends Job {
+ public string $domain;
+ public string $token;
+ public \DateTimeInterface $when;
+
+ public function __construct(string $domain, \DateTimeInterface $when) {
+ $this->domain = $domain;
+ $this->when = $when;
+ $this->remainingTries = 5;
+ }
+
+ public function run() {
+ Logger::getInstance()->info("refreshing outbound token for $this->domain");
+ $instance = Instance::findByDomain($this->domain);
+ $instance->refreshOutboundAuthToken();
+ }
+
+ public function submit() {
+ JobQueue::getInstance()->submitDelayedUntil($this, $this->when);
+ }
+} \ No newline at end of file
diff --git a/Digitigrade/Job/RequestOutboundAuthToken.php b/Digitigrade/Job/RequestOutboundAuthToken.php
new file mode 100644
index 0000000..69430c8
--- /dev/null
+++ b/Digitigrade/Job/RequestOutboundAuthToken.php
@@ -0,0 +1,28 @@
+<?php
+namespace Digitigrade\Job;
+
+use Digitigrade\Job;
+use Digitigrade\JobQueue;
+use Digitigrade\Logger;
+use Digitigrade\Model\Instance;
+
+class RequestOutboundAuthToken extends Job {
+ public string $domain;
+ public string $secret;
+
+ public function __construct(string $domain, string $secret) {
+ $this->domain = $domain;
+ $this->secret = $secret;
+ $this->remainingTries = 1;
+ }
+
+ public function run() {
+ Logger::getInstance()->info("responding to $this->domain's dialback challenge");
+ $instance = Instance::findByDomain($this->domain);
+ $instance->requestOutboundAuthToken($this->secret);
+ }
+
+ public function submit() {
+ JobQueue::getInstance()->submitUrgent($this);
+ }
+} \ No newline at end of file
diff --git a/Digitigrade/Job/UpdateInstanceInfo.php b/Digitigrade/Job/UpdateInstanceInfo.php
index 85c11f0..abcd182 100644
--- a/Digitigrade/Job/UpdateInstanceInfo.php
+++ b/Digitigrade/Job/UpdateInstanceInfo.php
@@ -2,6 +2,7 @@
namespace Digitigrade\Job;
use Digitigrade\Job;
+use Digitigrade\JobQueue;
use Digitigrade\Logger;
use Digitigrade\Model\Instance;
@@ -19,4 +20,8 @@ class UpdateInstanceInfo extends Job {
throw new \RuntimeException("remote object doesn't seem to exist");
}
}
+
+ public function submit() {
+ JobQueue::getInstance()->submitLazy($this);
+ }
} \ No newline at end of file
diff --git a/Digitigrade/JobQueue.php b/Digitigrade/JobQueue.php
index 9f95584..cce6be5 100644
--- a/Digitigrade/JobQueue.php
+++ b/Digitigrade/JobQueue.php
@@ -65,7 +65,7 @@ class JobQueue extends Singleton {
$job->run();
$this->conn->delete($beanJob);
$log->info('Job ' . $beanJob->getId() . ' completed successfully');
- } catch (\Exception $e) {
+ } catch (\Throwable $e) {
if ($job->remainingTries <= 0) {
$this->conn->delete($beanJob);
$log->error(
diff --git a/Digitigrade/Model.php b/Digitigrade/Model.php
index 84e6439..b5c9e99 100644
--- a/Digitigrade/Model.php
+++ b/Digitigrade/Model.php
@@ -55,7 +55,7 @@ abstract class Model {
foreach ($props as $p) {
$pName = $p->getName();
if (!isset($this->{$pName}))
- continue;
+ $this->{$pName} = null;
// we need to determine the plain value to insert ...
$value = $this->{$pName};
$discarded = false;
@@ -213,9 +213,10 @@ abstract class Model {
continue;
$type = $reflType->getName();
if (is_subclass_of($type, Model::class) && !is_subclass_of($type, FetchableModel::class)) {
- if (property_exists($this, 'id'))
+ if (isset($this->{$propName}) && property_exists($this, 'id')) {
$this->{$propName}->setOwnerId($this->id);
- $this->{$propName}->save();
+ $this->{$propName}->save();
+ }
}
}
diff --git a/Digitigrade/Model/FetchableModel.php b/Digitigrade/Model/FetchableModel.php
index 0b67599..e091a87 100644
--- a/Digitigrade/Model/FetchableModel.php
+++ b/Digitigrade/Model/FetchableModel.php
@@ -58,16 +58,20 @@ abstract class FetchableModel extends Model implements RemoteFetchable {
}
}
- $obj->beforeSave($uri);
+ $obj->finaliseAfterFetch($uri);
if ($autoSave) {
$obj->save();
+ $obj->finaliseAfterSave();
}
return $obj;
}
- protected function beforeSave(string $uri) {
+ protected function finaliseAfterFetch(string $uri) {
+ }
+
+ protected function finaliseAfterSave() {
}
public static function findByUri(string $uri, bool $autoSave = true, bool $forceRefetch = false): ?static {
diff --git a/Digitigrade/Model/Instance.php b/Digitigrade/Model/Instance.php
index de3ff9d..5f33e6d 100644
--- a/Digitigrade/Model/Instance.php
+++ b/Digitigrade/Model/Instance.php
@@ -1,7 +1,7 @@
<?php
namespace Digitigrade\Model;
-use Digitigrade\Model;
+use Digitigrade\Job\RefreshOutboundAuthToken;
class Instance extends FetchableModel {
public ?int $id;
@@ -15,6 +15,7 @@ class Instance extends FetchableModel {
public ?string $softwareHomepage;
public InstanceEndpoints $endpoints;
+ public InstanceAuth $auth;
protected function getUpdateWhereClause(\PDO $db): ?string {
if (self::findWhere('domain = ?', [$this->domain]) != null)
@@ -28,14 +29,25 @@ class Instance extends FetchableModel {
return self::findByUri("https://$domain/.well-known/pawpub-instance", $autoSave, $forceRefetch);
}
- public function hydrate() {
+ protected function hydrate() {
$this->endpoints = InstanceEndpoints::findWhere('instance_id = ?', [$this->id]);
+ $auth = InstanceAuth::findWhere('instance_id = ?', [$this->id]);
+ if ($auth == null) {
+ $auth = new InstanceAuth();
+ $auth->setOwnerId($this->id);
+ }
+ $this->auth = $auth;
}
- protected function beforeSave(string $uri) {
+ protected function finaliseAfterFetch(string $uri) {
$this->domain = explode('/', $uri)[2];
}
+ protected function finaliseAfterSave() {
+ // this has to be done after saving because we didn't have an id before
+ $this->auth = InstanceAuth::findWhere('instance_id = ?', [$this->id]) ?? new InstanceAuth();
+ }
+
public static function findByUri(string $uri, bool $autoSave = true, bool $forceRefetch = false): ?static {
if (!$forceRefetch) {
$domain = explode('/', $uri)[2];
@@ -45,4 +57,55 @@ class Instance extends FetchableModel {
}
return self::fetchFromRemote($uri, $autoSave);
}
+
+ public function beginOutboundAuth() {
+ $authEndpoint = $this->endpoints->auth;
+ if (!isset($authEndpoint)) {
+ throw new \RuntimeException("can't authenticate with $this->domain because it doesn't have an auth endpoint");
+ }
+ file_get_contents($authEndpoint . '?phase=dialback&target=' . urlencode(path_to_uri('/auth/dialback')));
+ }
+
+ public function requestOutboundAuthToken(string $secret) {
+ $authEndpoint = $this->endpoints->auth;
+ if (!isset($authEndpoint)) {
+ throw new \RuntimeException("can't authenticate with $this->domain because it doesn't have an auth endpoint");
+ }
+ $response = file_get_contents($authEndpoint . '?phase=token&secret=' . urlencode($secret));
+ if ($response === false) {
+ throw new \RuntimeException("requesting a token from $this->domain failed");
+ }
+ $data = json_decode($response);
+ if ($data === false || !isset($data->token, $data->expires)) {
+ throw new \RuntimeException("$this->domain sent bogus json in response to token request");
+ }
+
+ $this->auth->outboundToken = $data->token;
+ $this->save();
+
+ (new RefreshOutboundAuthToken($this->domain, new \DateTimeImmutable($data->expires)))->submit();
+ }
+
+ public function refreshOutboundAuthToken() {
+ $authEndpoint = $this->endpoints->auth;
+ if (!isset($authEndpoint)) {
+ throw new \RuntimeException("can't authenticate with $this->domain because it doesn't have an auth endpoint");
+ }
+ if (!isset($this->auth->outboundToken)) {
+ throw new \RuntimeException("can't refresh outbound token for $this->domain because i don't already have one");
+ }
+ $response = file_get_contents($authEndpoint . '?phase=refresh&token=' . urlencode($this->auth->outboundToken));
+ if ($response === false) {
+ throw new \RuntimeException("requesting a new token from $this->domain failed");
+ }
+ $data = json_decode($response);
+ if ($data === false || !isset($data->token, $data->expires)) {
+ throw new \RuntimeException("$this->domain sent bogus json in response to token refresh request");
+ }
+
+ $this->auth->outboundToken = $data->token;
+ $this->save();
+
+ (new RefreshOutboundAuthToken($this->domain, new \DateTimeImmutable($data->expires)))->submit();
+ }
} \ No newline at end of file
diff --git a/Digitigrade/Model/InstanceAuth.php b/Digitigrade/Model/InstanceAuth.php
new file mode 100644
index 0000000..616d4c5
--- /dev/null
+++ b/Digitigrade/Model/InstanceAuth.php
@@ -0,0 +1,21 @@
+<?php
+namespace Digitigrade\Model;
+
+use Digitigrade\Model;
+
+class InstanceAuth extends Model {
+ public ?int $instanceId;
+ public ?string $outboundToken;
+ public ?string $inboundToken;
+
+ protected function setOwnerId(int $id) {
+ $this->instanceId = $id;
+ }
+
+ protected function getUpdateWhereClause($db): ?string {
+ if (self::findWhere('instance_id = ?', [$this->instanceId]) != null) {
+ return "instance_id = $this->instanceId";
+ }
+ return null;
+ }
+} \ No newline at end of file