commit 0e6541b7da74113c463030190a7d2a5c31e3aaa2 Author: octospacc Date: Sat Sep 9 16:33:20 2023 +0200 [ActivityPub] v0.0.1-dev, add webfinger, nodeinfo, user profiles metadata diff --git a/phpBB/ext/spaccinc/activitypub/composer.json b/phpBB/ext/spaccinc/activitypub/composer.json new file mode 100755 index 0000000..3d89add --- /dev/null +++ b/phpBB/ext/spaccinc/activitypub/composer.json @@ -0,0 +1,27 @@ +{ + "name": "spaccinc/activitypub", + "type": "phpbb-extension", + "description": "ActivityPub for phpBB", + "homepage": "https://gitlab.com/SpaccInc/SpaccCommunityPlatform", + "version": "0.0.1-dev", + "time": "2023-09-07", + "license": "[to be defined]", + "authors": [ + { + "name": "Octt", + "email": "", + "homepage": "https://hub.octt.eu.org", + "role": "" + } + ], + "require": { + "php": ">=8.2.7", + "composer/installers": "~1.0" + }, + "extra": { + "display-name": "ActivityPub for phpBB", + "soft-require": { + "phpbb/phpbb": ">=3.3.0,<4.0.0@dev" + } + } +} diff --git a/phpBB/ext/spaccinc/activitypub/config/routing.yml b/phpBB/ext/spaccinc/activitypub/config/routing.yml new file mode 100755 index 0000000..fe97ec7 --- /dev/null +++ b/phpBB/ext/spaccinc/activitypub/config/routing.yml @@ -0,0 +1,12 @@ +spaccinc_activitypub_activitypub_webfinger: + path: /.well-known/webfinger + defaults: { _controller: spaccinc.activitypub.controller.activitypub:webfinger } + +spaccinc_activitypub_activitypub_nodeinfo_known: + path: /.well-known/nodeinfo + defaults: { _controller: spaccinc.activitypub.controller.activitypub:nodeinfo_known } + +spaccinc_activitypub_activitypub_activitypub: + path: /activitypub + defaults: { _controller: spaccinc.activitypub.controller.activitypub:activitypub } + diff --git a/phpBB/ext/spaccinc/activitypub/config/services.yml b/phpBB/ext/spaccinc/activitypub/config/services.yml new file mode 100755 index 0000000..34ba338 --- /dev/null +++ b/phpBB/ext/spaccinc/activitypub/config/services.yml @@ -0,0 +1,7 @@ +services: + spaccinc.activitypub.controller.activitypub: + class: spaccinc\activitypub\controller\activitypub_controller + arguments: + - '@request' + - '@dbal.conn' + diff --git a/phpBB/ext/spaccinc/activitypub/controller/activitypub_controller.php b/phpBB/ext/spaccinc/activitypub/controller/activitypub_controller.php new file mode 100755 index 0000000..4785aa1 --- /dev/null +++ b/phpBB/ext/spaccinc/activitypub/controller/activitypub_controller.php @@ -0,0 +1,270 @@ +request = $request; + $this->db = $db; + + $this->site_name = $this->get_sql_row(' + SELECT config_value + FROM ' . CONFIG_TABLE . " + WHERE config_name = 'sitename' + ")['config_value']; + + $this->server_name = $this->get_sql_row(' + SELECT config_value + FROM ' . CONFIG_TABLE . " + WHERE config_name = 'server_name' + ")['config_value']; + + $this->server_addr = ((!empty($this->request->server('HTTPS')) && (strtolower($this->request->server('HTTPS')) == 'on' || $this->request->server('HTTPS') == '1')) ? 'https://' : 'http://') . $this->server_name; + } + + //private function server_name() + //{ + // // + // //return strtolower(htmlspecialchars_decode($this->request->header('Host', $this->request->server('SERVER_NAME')))); + //} + + //private function server_addr() + //{ + // // + // $proto = (!empty($this->request->server('HTTPS')) && (strtolower($this->request->server('HTTPS')) == 'on' || $this->request->server('HTTPS') == '1')) ? 'https://' : 'http://'; + // return $proto . $this->server_name; + //} + + private function get_sql_row($sql) + { + $result = $this->db->sql_query($sql); + $data = $this->db->sql_fetchrow($result); + $this->db->sql_freeresult($result); + return $data; + } + + public function nodeinfo_known() + { + set_error_handler([$this, 'exception_error_handler']); + $server_addr = $this->server_addr; + $response = new Response(json_encode([ + 'links' => [[ + 'rel' => 'http://nodeinfo.diaspora.software/ns/schema/2.0', + 'href' => $server_addr . '/activitypub?&mode=nodeinfo&version=2.0', + ]], + ]), 200); + $response->headers->set('Content-Type', 'application/jrd+json; charset=utf-8'); + restore_error_handler(); + return $response; + } + + public function webfinger() + { + set_error_handler([$this, 'exception_error_handler']); + $server_name = $this->server_name; + $server_addr = $this->server_addr; + $resource = $this->request->variable('resource', ''); + + $subject = ''; + $href = []; + + //if ($resource == '') + //{ + // return new JsonResponse([], 500); + //} + + if (str_starts_with($resource, 'acct:')) + { + $tokens = explode('@', substr($resource, strlen('acct:'))); + if ($tokens[0] === '') + { + array_shift($tokens); + } + $name = strtolower($tokens[0]); + $subject = 'acct:' . $name . '@' . $server_name; + + $data = $this->get_sql_row(' + SELECT user_id + FROM ' . USERS_TABLE . " + WHERE username_clean = '" . $this->db->sql_escape($name) . "' + "); + + $href['self'] = '&mode=user&u=' . $data['user_id']; + $href['profile-page'] = '/memberlist.php?mode=viewprofile&u=' . $data['user_id']; + } + + $response = new Response(json_encode([ + 'subject' => $subject, + 'links' => [[ + 'rel' => 'self', + 'type' => 'application/activity+json', + 'href' => $server_addr . '/activitypub?' . $href['self'], + ], [ + 'rel' => 'http://webfinger.net/rel/profile-page', + 'type' => 'text/html', + 'href' => $server_addr . $href['profile-page'], + ]], + ]), 200); + $response->headers->set('Content-Type', 'application/jrd+json; charset=utf-8'); + restore_error_handler(); + return $response; + } + + public function activitypub() + { + set_error_handler([$this, 'exception_error_handler']); + $server_addr = $this->server_addr; + $uri_id = htmlspecialchars_decode($server_addr . $this->request->server('REQUEST_URI')); + $mode = $this->request->variable('mode', ''); + + $response = []; + + switch ($mode) + { + case 'nodeinfo': + $version = $this->request->variable('version', ''); + switch ($version) + { + //case '1.0': + // // ... + //break; + + case '2.0': + $response = [ + 'version' => '2.0', + 'software' => [ + 'name' => 'phpBB ActivityPub', + 'version' => '0.0.1', + ], + 'protocols' => [ + 'activitypub', + ], + 'services' => [ + 'inbound' => [], + 'outbound' => [], + ], + //'usage' => [ + // 'users' => [ + // 'total' => , + // ], + // 'localPosts' => , + //], + //'openRegistrations' => true, + 'metadata' => [ + 'nodeName' => $this->site_name, + ], + ]; + break; + } + break; + + case 'user': + $u = $this->request->variable('u', ''); + + $data = $this->get_sql_row(' + SELECT * + FROM ' . USERS_TABLE . " + WHERE user_id = '" . $this->db->sql_escape($u) . "' + "); + + $icon_ext = end(explode('.', $data['user_avatar'])); + +//$config = [ +// "private_key_bits" => 2048, +// "private_key_type" => OPENSSL_KEYTYPE_RSA, +//]; +// +//$keypair = openssl_pkey_new($config); +//openssl_pkey_export($keypair, $private_key); +// +//$public_key = openssl_pkey_get_details($keypair); +//$public_key = $public_key["key"]; + + $response = [ + '@context' => [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1', + ], + 'id' => $uri_id, + 'inbox' => $server_addr . '/activitypub?&mode=inbox&u=' . $u, + 'outbox' => $server_addr . '/activitypub?&mode=outbox&u=' . $u, + 'endpoints' => [ + 'sharedInbox' => $server_addr . '/activitypub&mode=inbox', + ], + 'type' => 'Person', + //'discoverable' => true, + //'manuallyApprovesFollowers' => false, + //'published' => $data['user_regdate'], + 'preferredUsername' => $data['username_clean'], + 'name' => $data['username'], + 'url' => $server_addr . '/memberlist.php?mode=viewprofile&u=' . $u, + 'icon' => [ + 'type' => 'Image', + 'mediaType' => 'image/' . ($icon_ext === 'jpg' ? 'jpeg' : $icon_ext), + 'url' => $server_addr . '/download/file.php?avatar=' . $data['user_avatar'], + ], + //'publicKey' => [ + // 'id' => $uri_id . '#main-key', + // 'owner' => $uri_id, + // 'publicKeyPem' => $public_key, + //], + ]; + break; + + case 'inbox': + + break; + + case 'outbox': + $u = $this->request->variable('u', ''); + + $user_posts = $this->get_sql_row(' + SELECT user_posts + FROM ' . USERS_TABLE . " + WHERE user_id = '" . $this->db->sql_escape($u) . "' + ")['user_posts']; + + $response = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'id' => $uri_id, + 'type' => 'OrderedCollection', + 'totalItems' => (int)$user_posts, + 'first' => $uri_id . '&view=page', + 'last' => $uri_id . '&view=page&min_id=0', + ]; + break; + + //case 'post': + // + //break; + } + + $response = new Response(json_encode($response), 200); + $response->headers->set('Content-Type', 'application/activity+json; charset=utf-8'); + restore_error_handler(); + return $response; + } +}