diff --git a/README.md b/README.md
index 4598b36..e4c8412 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,10 @@ Needed SQL commands:
create user if not exists 'federatoradmin'@'localhost' identified by '*change*me*as*well';
grant all privileges on federator.* to 'federatoradmin'@'localhost';
-This will be changed, but works for the current develop verison.
+After this, change the settings in the ini file to match above changed passwords.
+change to php/federator and run
+ php maintenance dbupgrade
+to install all the needed table. Also run this after an update.
If the include redis cache is enabled, create a users.acl for redis with the content:
@@ -38,8 +41,15 @@ To configure an apache server, add the following rewrite rules:
RewriteEngine on
RewriteBase /
- RewriteRule ^api/federator/(.+)$ federator.php?_call=$1 [L]
+ RewriteCond expr "%{HTTP:accept} -strcmatch '*application/ld+json*'" [OR]
+ RewriteCond expr "%{HTTP:accept} -strcmatch '*application/jrd+json*'" [OR]
+ RewriteCond expr "%{HTTP:accept} -strcmatch '*application/activity+json*'" [OR]
+ RewriteCond expr "%{HTTP:content-type} -strcmatch '*application/activity+json*'"
+ RewriteRule ^@(.*)$ /federator.php?_call=fedusers/$1 [L,END]
+ RewriteRule ^users/(.*)$ /federator.php?_call=fedusers/$1 [L,END]
+ RewriteRule ^api/federator/(.+)$ federator.php?_call=$1 [L,END]
RewriteRule ^(\.well-known/.*)$ /federator.php?_call=$1 [L,END]
+ RewriteRule ^(nodeinfo/2\.[01])$ /federator.php?_call=$1 [L,END]
With the dummy plugin and everything installed correctly a
@@ -47,4 +57,3 @@ With the dummy plugin and everything installed correctly a
> curl -v http://localhost/api/federator/v1/dummy/moo -H "X-Session: somethingvalid" -H "X-Profile: ihaveone"
should return a piece of ascii art.
-
diff --git a/php/federator/api.php b/php/federator/api.php
index 5134850..83ff2e2 100644
--- a/php/federator/api.php
+++ b/php/federator/api.php
@@ -98,6 +98,9 @@ class Api extends Main
case 'nodeinfo':
$handler = new Api\WellKnown($this);
break;
+ case 'fedusers':
+ $handler = new Api\FedUsers($this);
+ break;
case 'v1':
switch ($this->paths[1]) {
case 'dummy':
diff --git a/php/federator/api/fedusers.php b/php/federator/api/fedusers.php
new file mode 100644
index 0000000..fb1d93b
--- /dev/null
+++ b/php/federator/api/fedusers.php
@@ -0,0 +1,116 @@
+main = $main;
+ }
+
+ /**
+ * run given url path
+ *
+ * @param array $paths path array split by /
+ * @param \Federator\Data\User|false $user user who is calling us @unused-param
+ * @return bool true on success
+ */
+ public function exec($paths, $user)
+ {
+ $method = $_SERVER["REQUEST_METHOD"];
+ switch ($method) {
+ case 'GET':
+ switch (sizeof($paths)) {
+ case 2:
+ // /users/username or /@username
+ return $this->returnUserProfile($paths[1]);
+ }
+ break;
+ }
+ $this->main->setResponseCode(404);
+ return false;
+ }
+
+
+ /**
+ * return user profile
+ *
+ * @param string $_name
+ * @eturn boolean true on success
+ */
+ private function returnUserProfile($_name)
+ {
+ $user = \Federator\DIO\User::getUserByName(
+ $this->main->getDatabase(),
+ $_name,
+ $this->main->getConnector(),
+ $this->main->getCache()
+ );
+ if ($user === false || $user->id === null) {
+ throw new \Federator\Exceptions\FileNotFound();
+ }
+ $data = [
+ 'iconMediaType' => $user->iconMediaType,
+ 'iconURL' => $user->iconURL,
+ 'imageMediaType' => $user->imageMediaType,
+ 'imageURL' => $user->imageURL,
+ 'fqdn' => $_SERVER['SERVER_NAME'],
+ 'name' => $user->name,
+ 'username' => $user->id,
+ 'publickey' => str_replace("\n", "\\n", $user->publicKey),
+ 'registered' => gmdate('Y-m-d\TH:i:s\Z', $user->registered), // 2021-03-25T00:00:00Z
+ 'summary' => $user->summary,
+ 'type' => $user->type
+ ];
+ $this->response = $this->main->renderTemplate('user.json', $data);
+ return true;
+ }
+ /**
+ * set response
+ *
+ * @param string $response response to set
+ * @return void
+ */
+ public function setResponse($response)
+ {
+ $this->response = $response;
+ }
+
+ /**
+ * get internal represenation as json string
+ * @return string json string or html
+ */
+ public function toJson()
+ {
+ return $this->response;
+ }
+}
diff --git a/php/federator/data/user.php b/php/federator/data/user.php
index bbc7015..efb829d 100644
--- a/php/federator/data/user.php
+++ b/php/federator/data/user.php
@@ -20,6 +20,20 @@ class User
*/
public $externalid;
+ /**
+ * icon media type
+ *
+ * @var string $iconMediaType
+ */
+ public $iconMediaType;
+
+ /**
+ * icon url
+ *
+ * @var string $iconURL
+ */
+ public $iconURL;
+
/**
* user id
*
@@ -27,9 +41,29 @@ class User
*/
public $id;
+ /**
+ * image media type
+ *
+ * @var string $imageMediaType
+ */
+ public $imageMediaType;
+
+ /**
+ * image url
+ *
+ * @var string $imageURL
+ */
+ public $imageURL;
+
/* @var string user language */
//public $lang;
+ /**
+ * user name
+ *
+ * @var string $name
+ */
+ public $name = '';
/**
* user permissions
*
@@ -38,12 +72,40 @@ class User
*/
public $permissions = [];
+ /**
+ * user public key
+ *
+ * @var string $publicKey
+ */
+ public $publicKey;
+
+ /**
+ * registered unix timestamp
+ *
+ * @var int $registered
+ */
+ public $registered = 0;
+
/**
* session id
*
* @var string $session
* */
- public $session;
+ public $session = '';
+
+ /**
+ * summary for user/profile
+ *
+ * @var string $summary
+ */
+ public $summary = '';
+
+ /**
+ * type of user (user/group)
+ *
+ * @var string $type
+ */
+ public $type = 'group';
/**
* create new user object from json string
@@ -58,10 +120,18 @@ class User
return false;
}
$user = new User();
+ $user->iconMediaType = $data['iconMediaType'];
+ $user->iconURL = $data['iconURL'];
$user->id = $data['id'];
+ $user->imageMediaType = $data['imageMediaType'];
+ $user->imageURL = $data['imageURL'];
$user->externalid = $data['externalid'];
+ $user->name = $data['name'];
/// TODO: replace with enums
$user->permissions = $data['permissions'];
+ $user->publicKey = $data['publicKey'];
+ $user->summary = $data['summary'];
+ $user->type = $data['type'];
return $user;
}
@@ -86,9 +156,17 @@ class User
public function toJson()
{
$data = [
- 'id' => $this->id,
- 'externalid' => $this->externalid,
- 'permissions' => $this->permissions
+ 'iconMediaType' => $this->iconMediaType,
+ 'iconURL' => $this->iconURL,
+ 'id' => $this->id,
+ 'imageMediaType' => $this->iconMediaType,
+ 'imageURL' => $this->iconURL,
+ 'externalid' => $this->externalid,
+ 'name' => $this->name,
+ 'permissions' => $this->permissions,
+ 'publicKey' => $this->publicKey,
+ 'summary' => $this->summary,
+ 'type' => $this->type
];
return json_encode($data) | '';
}
diff --git a/php/federator/dio/user.php b/php/federator/dio/user.php
index a625959..82084fa 100644
--- a/php/federator/dio/user.php
+++ b/php/federator/dio/user.php
@@ -22,27 +22,78 @@ class User
*/
protected static function addLocalUser($dbh, $user, $_user)
{
- // needed fields: RSA key pair, user name (handle)
- $private_key = openssl_pkey_new();
- if ($private_key === false) {
+ // check if it is timed out user
+ $sql = 'select unix_timestamp(`validuntil`) from users where id=?';
+ $stmt = $dbh->prepare($sql);
+ if ($stmt === false) {
throw new \Federator\Exceptions\ServerError();
}
- $public = openssl_pkey_get_details($private_key)['key'];
- $private = '';
- openssl_pkey_export($private_key, $private);
- try {
- $sql = 'insert into users (id, externalid, rsapublic, rsaprivate, validuntil)';
- $sql .= ' values (?, ?, ?, ?, now() + interval 1 day)';
- $sql .= ' on duplicate key update validuntil=now() + interval 1 day';
+ $stmt->bind_param("s", $_user);
+ $validuntil = 0;
+ $ret = $stmt->bind_result($validuntil);
+ $stmt->execute();
+ if ($ret) {
+ $stmt->fetch();
+ }
+ $stmt->close();
+ if ($validuntil == 0) {
+ $private_key = openssl_pkey_new();
+ if ($private_key === false) {
+ throw new \Federator\Exceptions\ServerError();
+ }
+ $public = openssl_pkey_get_details($private_key)['key'];
+ $private = '';
+ openssl_pkey_export($private_key, $private);
+ $sql = 'insert into users (id, externalid, rsapublic, rsaprivate, validuntil, type, name, summary, registered, iconmediatype, iconurl, imagemediatype, imageurl)';
+ $sql .= ' values (?, ?, ?, ?, now() + interval 1 day, ?, ?, ?, ?, ?, ?, ?, ?)';
$stmt = $dbh->prepare($sql);
if ($stmt === false) {
throw new \Federator\Exceptions\ServerError();
}
- $stmt->bind_param("ssss", $_user, $user->externalid, $public, $private);
+ $registered = gmdate('Y-m-d H:i:s', $user->registered);
+ $stmt->bind_param(
+ "ssssssssssss",
+ $_user,
+ $user->externalid,
+ $public,
+ $private,
+ $user->type,
+ $user->name,
+ $user->summary,
+ $registered,
+ $user->iconMediaType,
+ $user->iconURL,
+ $user->imageMediaType,
+ $user->imageURL
+ );
+ } else {
+ // update to existing user
+ $sql = 'update users set validuntil=now() + interval 1 day, type=?, name=?, summary=?, registered=?, iconmediatype=?, iconurl=?, imagemediatype=?, imageurl=? where id=?';
+ $stmt = $dbh->prepare($sql);
+ if ($stmt === false) {
+ throw new \Federator\Exceptions\ServerError();
+ }
+ $registered = gmdate('Y-m-d H:i:s', $user->registered);
+ $stmt->bind_param(
+ "sssssssss",
+ $user->type,
+ $user->name,
+ $user->summary,
+ $registered,
+ $user->iconMediaType,
+ $user->iconURL,
+ $user->imageMediaType,
+ $user->imageURL,
+ $_user
+ );
+ }
+ try {
$stmt->execute();
$stmt->close();
$user->id = $_user;
} catch (\mysqli_sql_exception $e) {
+ error_log($sql);
+ error_log(print_r($user, true));
error_log($e->getMessage());
}
}
@@ -55,20 +106,21 @@ class User
*/
protected static function extendUser(\mysqli $dbh, $user, $_user) : void
{
- $sql = 'select id from users where id=?';
+ $sql = 'select id,unix_timestamp(`validuntil`) from users where id=?';
$stmt = $dbh->prepare($sql);
if ($stmt === false) {
throw new \Federator\Exceptions\ServerError();
}
$stmt->bind_param("s", $_user);
- $ret = $stmt->bind_result($user->id);
+ $validuntil = 0;
+ $ret = $stmt->bind_result($user->id, $validuntil);
$stmt->execute();
if ($ret) {
$stmt->fetch();
}
$stmt->close();
// if a new user, create own database entry with additionally needed info
- if ($user->id === null) {
+ if ($user->id === null || $validuntil < time()) {
self::addLocalUser($dbh, $user, $_user);
}
@@ -99,14 +151,26 @@ class User
return $user;
}
// check our db
- $sql = 'select id,externalid from users where id=? and validuntil>=now()';
+ $sql = 'select id,externalid,type,name,summary,unix_timestamp(registered),rsapublic,iconmediatype,iconurl,imagemediatype,imageurl from users where id=? and validuntil>=now()';
$stmt = $dbh->prepare($sql);
if ($stmt === false) {
throw new \Federator\Exceptions\ServerError();
}
$stmt->bind_param("s", $_name);
$user = new \Federator\Data\User();
- $ret = $stmt->bind_result($user->id, $user->externalid);
+ $ret = $stmt->bind_result(
+ $user->id,
+ $user->externalid,
+ $user->type,
+ $user->name,
+ $user->summary,
+ $user->registered,
+ $user->publicKey,
+ $user->iconMediaType,
+ $user->iconURL,
+ $user->imageMediaType,
+ $user->imageURL,
+ );
$stmt->execute();
if ($ret) {
$stmt->fetch();
diff --git a/plugins/federator/contentnation.php b/plugins/federator/contentnation.php
index 05855d6..7688491 100644
--- a/plugins/federator/contentnation.php
+++ b/plugins/federator/contentnation.php
@@ -83,11 +83,16 @@ class ContentNation implements Connector
if ($r === false || $r === null || !is_array($r)) {
return false;
}
- if (!array_key_exists('name', $r) || $r['name'] !== $_name) {
- return false;
- }
$user = new \Federator\Data\User();
$user->externalid = $_name;
+ $user->iconMediaType = $r['iconMediaType'];
+ $user->iconURL = $r['iconURL'];
+ $user->imageMediaType = $r['imageMediaType'];
+ $user->imageURL = $r['imageURL'];
+ $user->name = $r['name'];
+ $user->summary = $r['summary'];
+ $user->type = $r['type'];
+ $user->registered = intval($r['registered'], 10);
return $user;
}
@@ -118,8 +123,8 @@ class ContentNation implements Connector
if ($r === false || !is_array($r) || !array_key_exists($_user, $r)) {
return false;
}
- $user = new \Federator\Data\User();
- $user->externalid = $_user;
+ $user = $this->getRemoteUserByName($_user);
+ // extend with permissions
$user->permissions = [];
$user->session = $_session;
foreach ($r[$_user] as $p) {
diff --git a/plugins/federator/dummyconnector.php b/plugins/federator/dummyconnector.php
index 9aef028..ee98424 100644
--- a/plugins/federator/dummyconnector.php
+++ b/plugins/federator/dummyconnector.php
@@ -43,8 +43,6 @@ class DummyConnector implements Connector
// validate $_session and $user
$user = new \Federator\Data\User();
$user->externalid = $_name;
- $user->permissions = [];
- $user->session = '';
return $user;
}
diff --git a/sql/2024-07-23.sql b/sql/2024-07-23.sql
new file mode 100644
index 0000000..04aff80
--- /dev/null
+++ b/sql/2024-07-23.sql
@@ -0,0 +1,9 @@
+alter table users add `type` enum('person', 'group') default 'person';
+alter table users add `name` varchar(255) default '';
+alter table users add `summary` text default '';
+alter table users add `registered` timestamp default 0;
+alter table users add `iconmediatype` varchar(255) default '';
+alter table users add `iconurl` varchar(255) default '';
+alter table users add `imagemediatype` varchar(255) default '';
+alter table users add `imageurl` varchar(255) default '';
+update settings set `value`="2024-07-23" where `key`="database_version";
diff --git a/templates/federator/user.json b/templates/federator/user.json
new file mode 100644
index 0000000..752eb80
--- /dev/null
+++ b/templates/federator/user.json
@@ -0,0 +1,104 @@
+{ldelim}
+ "@context":[
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ {ldelim}
+ "manuallyApprovesFollowers":"as:manuallyApprovesFollowers",
+ "toot":"http://joinmastodon.org/ns#",
+ "featured":{ldelim}
+ "@id":"toot:featured",
+ "@type":"@id"
+ {rdelim},
+ "featuredTags":{ldelim}
+ "@id":"toot:featuredTags",
+ "@type":"@id"
+ {rdelim},
+ "alsoKnownAs":{ldelim}
+ "@id":"as:alsoKnownAs",
+ "@type":"@id"
+ {rdelim},
+ "movedTo":{ldelim}
+ "@id":"as:movedTo",
+ "@type":"@id"
+ {rdelim},
+ "schema":"http://schema.org#",
+ "PropertyValue":"schema:PropertyValue",
+ "value":"schema:value",
+ "discoverable":"toot:discoverable",
+ "Device":"toot:Device",
+ "Ed25519Signature":"toot:Ed25519Signature",
+ "Ed25519Key":"toot:Ed25519Key",
+ "Curve25519Key":"toot:Curve25519Key",
+ "EncryptedMessage":"toot:EncryptedMessage",
+ "publicKeyBase64":"toot:publicKeyBase64",
+ "deviceId":"toot:deviceId",
+ "claim":{ldelim}
+ "@type":"@id",
+ "@id":"toot:claim"
+ {rdelim},
+ "fingerprintKey":{ldelim}
+ "@type":"@id",
+ "@id":"toot:fingerprintKey"
+ {rdelim},
+ "identityKey":{ldelim}
+ "@type":"@id",
+ "@id":"toot:identityKey"
+ {rdelim},
+ "devices":{ldelim}
+ "@type":"@id",
+ "@id":"toot:devices"
+ {rdelim},
+ "messageFranking":"toot:messageFranking",
+ "messageType":"toot:messageType",
+ "cipherText":"toot:cipherText",
+ "suspended":"toot:suspended",
+ "focalPoint":{ldelim}
+ "@container":"@list",
+ "@id":"toot:focalPoint"
+ {rdelim}
+ {rdelim}
+ ],
+ "id":"https://{$fqdn}/users/{$username}",
+ "type":"{$type}",
+ "following":"https://{$fqdn}/users/{$username}/following",
+ "followers":"https://{$fqdn}/users/{$username}/followers",
+ "inbox":"https://{$fqdn}/users/{$username}/inbox",
+ "outbox":"https://{$fqdn}/users/{$username}/outbox",
+ "featured":"https://{$fqdn}/users/{$username}/collections/featured",
+ "featuredTags":"https://{$fqdn}/users/{$username}/collections/tags",
+ "preferredUsername":"{$username}",
+ "name":"{$name}",
+ "summary":"{$summary}",
+ "url":"https://{$fqdn}/@{$username}",
+ "manuallyApprovesFollowers":false,
+ "discoverable":true,
+ "published":"{$registered}",
+ "publicKey":{ldelim}
+ "id":"https://{$fqdn}/users/{$username}#main-key",
+ "owner":"https://{$fqdn}/users/{$username}",
+ "publicKeyPem":"{$publickey}"
+ {rdelim},
+ "tag":[],
+ "attachment":[
+ {if $type==='group'}{ldelim}
+ "type":"PropertyValue",
+ "name":"website",
+ "value":"\u003ca href=\"https://{$fqdn}/@{$username}\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\"\u003e\u003cspan class=\"invisible\"\u003ehttps://\u003c/span\u003e\u003cspan class=\"\"\u003e{$fqdn}/@{$username}\u003c/span\u003e\u003cspan class=\"invisible\"\u003e\u003c/span\u003e\u003c/a\u003e"
+ {rdelim}{/if}
+ ],
+ "endpoints":{ldelim}
+ "sharedInbox":"https://{$fqdn}/inbox"
+ {rdelim},
+{if $iconURL !== ''} "icon":{ldelim}
+ "type":"Image",
+ "mediaType":"{$iconMediaType}",
+ "url":"{$iconURL}"
+ {rdelim},
+{/if}
+{if $imageURL !== ''} "image":{ldelim}
+ "type":"Image",
+ "mediaType":"{$imageMediaType}",
+ "url":"{$imageURL}"
+ {rdelim}
+{/if}
+{rdelim}
diff --git a/templates/federator/webfinger_acct.json b/templates/federator/webfinger_acct.json
index 4777c44..ab532ce 100644
--- a/templates/federator/webfinger_acct.json
+++ b/templates/federator/webfinger_acct.json
@@ -1,6 +1,9 @@
{ldelim}
"subject": "acct:{$username}@{$domain}",
- "aliases": ["https://{$domain}/@{$username}"],
+ "aliases": [
+ "https://{$domain}/@{$username}"
+ "https://{$domain}/users/{$username}"
+ ],
"links": [
{ldelim}"rel": "self", "type": "application/activity+json", "href": "https://{$domain}/{$username}"{rdelim},
{if $type=='Group'}